docs: function doFilter(tracer, err, serverId, msg, opts, filters, index, operate, cb) {
      if (index < filters.length) {
          tracer.info('client', __filename, 'doFilter', `do ${operate} filter ${filters[index].name}`);
      }
      if (index >= filters.length || !!err) {
          utils.invokeCallback(cb, tracer, err, serverId, msg, opts);
          return;
      }
      const filter = filters[index];
      if (typeof filter === 'function') {
          filter(serverId, msg, opts, (target, message, options) => {
              index++;
              // compatible for pomelo filter next(err) method
              if (utils.getObjectClass(target) === 'Error') {
                  doFilter(tracer, target, serverId, msg, opts, filters, index, operate, cb);
              } else {
                  doFilter(tracer, null, target || serverId, message || msg, options || opts, filters, index, operate, cb);
              }
          });
          return;
      }
      if (typeof filter[operate] === 'function') {
          filter[operate](serverId, msg, opts, (target, message, options) => {
              index++;
              if (utils.getObjectClass(target) === 'Error') {
                  doFilter(tracer, target, serverId, msg, opts, filters, index, operate, cb);
              } else {
                  doFilter(tracer, null, target || serverId, message || msg, options || opts, filters, index, operate, cb);
              }
          });
          return;
      }
      index++;
      doFilter(tracer, err, serverId, msg, opts, filters, index, operate, cb);
  }