docs: def create_matcher(dispatcher, parsers, apptags, matcher='ruled', hosts=tuple(), time_range=None,
                     time_period=(None, None), patterns=tuple(), invert=False, count=False,
                     files_with_match=None, max_count=0, only_matching=False, quiet=False,
                     thread=False, name_cache=None):
      """
      Create a matcher engine.
      :return: A matcher function.
      """
      parsers = CycleParsers(parsers)
      max_matches = 1 if quiet else max_count
      use_app_rules = matcher != 'unruled'
      select_unparsed = matcher == 'unparsed'
      register_log_lines = not (quiet or count or files_with_match is not None)
      start_dt, end_dt = get_mktime_period(time_period)
      pattern_search = create_search_function(invert, only_matching)
      dispatch_selected = dispatcher.dispatch_selected
      dispatch_context = dispatcher.dispatch_context
      display_progress_bar = sys.stdout.isatty() and all(c.name != 'stdout' for c in dispatcher.channels)
  
      def process_logfile(source, apps, encoding='utf-8'):
          log_parser = next(parsers)
          first_event = None
          last_event = None
          app_thread = None
          selected_data = None
          line_counter = 0
          unknown_counter = 0
          selected_counter = 0
          extra_tags = Counter()
          dispatcher.reset()
          read_size = 0
          progress_bar = None
  
          with open_resource(source) as logfile:
              # Set counters and status
              logfile_name = logfile.name
  
              fstat = os.fstat(logfile.fileno())
              file_mtime = datetime.datetime.fromtimestamp(fstat.st_mtime)
              file_year = file_mtime.year
              file_month = file_mtime.month
              prev_year = file_year - 1
  
              if display_progress_bar:
                  read_size = 0
                  progress_bar = ProgressBar(sys.stdout, fstat.st_size, logfile_name)
  
              for line in logfile:
                  line = line.decode(encoding)
                  line_counter += 1
                  if line[-1] != '\n':
                      line += '\n'
  
                  if display_progress_bar:
                      read_size += len(line)
                      if not line_counter % 100:
                          progress_bar.redraw(read_size)
  
                  ###
                  # Parses the line and extracts the log data
                  log_match = log_parser.match(line)
                  if log_match is None:
                      # The current parser doesn't match: try another available parser.
                      next_parser, log_match = parsers.detect(line)
                      if log_match is not None:
                          log_parser = next_parser
                      elif line_counter == 1:
                          logger.warning("the file '{}' has an unknown format, skip ...".format(logfile_name))
                          break
                      else:
                          unknown_counter += 1
                          continue
                  log_data = log_parser.get_data(log_match)
  
                  ###
                  # Process last event repetition (eg. 'last message repeated N times' RFC 3164's logs)
                  if getattr(log_data, 'repeat', None) is not None:
                      if selected_data is not None:
                          repeat = int(log_data.repeat)
                          if not thread:
                              selected_counter += repeat
                          if use_app_rules:
                              app = log_parser.app or get_app(selected_data, apps, apptags, extra_tags)
                              app.increase_last(repeat)
                              app.matches += 1
                              dispatch_context(
                                  key=(app, app_thread),
                                  filename=logfile_name,
                                  line_number=line_counter,
                                  rawlog=line
                              )
                          selected_data = None
                      continue
                  selected_data = None
  
                  ###
                  # Parse the log's timestamp and gets the event datetime
                  year = getattr(
                      log_data, 'year',
                      prev_year if MONTHMAP[log_data.month] != '01' and file_month == 1 else file_year
                  )
                  event_dt = get_mktime(
                      year=year,
                      month=log_data.month,
                      day=log_data.day,
                      ltime=log_data.ltime
                  )
  
                  ###
                  # Scope exclusions
                  if event_dt is None or event_dt < start_dt:
                      # Excludes lines older than the start datetime
                      continue
                  elif event_dt > end_dt:
                      # Excludes lines newer than the end datetime
                      if fstat.st_mtime < event_dt:
                          logger.error("found anomaly with mtime of file %r at line %d", logfile_name, line_counter)
                      logger.warning("newer event at line %d: skip the rest of the file %r", line_counter, logfile_name)
                      break
                  elif time_range is not None and not time_range.between(log_data.ltime):
                      # Excludes lines not in time range
                      continue
                  elif hosts and not has_host_match(log_data, hosts):
                      # Excludes lines with host restriction
                      continue
  
                  ###
                  # Search log line with provided not-empty pattern(s)
                  pattern_matched, match, rawlog = pattern_search(line, patterns)
                  if not pattern_matched and not thread:
                      dispatch_context(filename=logfile_name, line_number=line_counter, rawlog=rawlog)
                      continue
  
                  ###
                  # App parsing: get the app from parser or from the log data
                  app = log_parser.app or get_app(log_data, apps, apptags, extra_tags)
                  if app is None:
                      # Unmatchable tag --> skip the line
                      continue
                  elif use_app_rules:
                      # Parse the log message with app's rules
                      app_matched, has_full_match, app_thread, output_data = app.match_rules(log_data)
                      if not pattern_matched and app_matched and app_thread is None:
                          continue
                      if output_data:
                          rawlog = name_cache.match_to_string(log_match, log_parser.parser.groupindex, output_data)
  
                      if app_matched:
                          app.matches += 1
                          if not has_full_match or select_unparsed:
                              dispatch_context(
                                  key=(app, app_thread),
                                  filename=logfile_name,
                                  line_number=line_counter,
                                  rawlog=rawlog
                              )
                              continue
                      else:
                          app.unparsed += 1
                          if not select_unparsed:
                              dispatch_context(
                                  key=(app, app_thread),
                                  filename=logfile_name,
                                  line_number=line_counter,
                                  rawlog=rawlog
                              )
                              continue
  
                  ###
                  # Event selected: register event's data and datetime
                  selected_data = log_data
  
                  if first_event is None:
                      first_event = event_dt
                      last_event = event_dt
                  else:
                      if first_event > event_dt:
                          first_event = event_dt
                      if last_event < event_dt:
                          last_event = event_dt
  
                  if pattern_matched:
                      if max_matches and selected_counter >= max_matches:
                          # Stops iteration if max_count matches is exceeded
                          break
                      selected_counter += 1
                      if files_with_match:
                          break
                      if register_log_lines:
                          dispatch_selected(
                              key=(app, app_thread),
                              filename=logfile_name,
                              line_number=line_counter,
                              log_data=log_data,
                              rawlog=rawlog,
                              match=match
                          )
                  elif register_log_lines and not only_matching:
                      # Thread matching
                      dispatch_context(
                          key=(app, app_thread),
                          filename=logfile_name,
                          line_number=line_counter,
                          rawlog=rawlog
                      )
  
              if display_progress_bar:
                  progress_bar.redraw(fstat.st_size)
  
          try:
              for key in list(dispatcher.keys()):
                  dispatcher.flush(key)
          except (NameError, AttributeError):
              pass
  
          # If count option is enabled then register only the number of matched lines.
          if files_with_match and selected_counter or files_with_match is False and not selected_counter:
              dispatch_selected(filename=logfile.name)
          elif count:
              dispatch_selected(filename=logfile.name, counter=selected_counter)
  
          return MatcherResult(
              lines=line_counter,
              matches=selected_counter,
              unknown=unknown_counter,
              extra_tags=extra_tags,
              first_event=first_event,
              last_event=last_event
          )
  
      return process_logfile