Create a matcher engine.
    :return: A matcher function.

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