diff --git a/.github/workflows/testing-go.yml b/.github/workflows/testing-go.yml
index 7c9be4ab..c3467473 100644
--- a/.github/workflows/testing-go.yml
+++ b/.github/workflows/testing-go.yml
@@ -23,7 +23,17 @@ jobs:
       matrix:
         os-version: ['ubuntu-22.04', 'macos-latest' ]
         go-version: [ '1.20', '1.21' ]
-        package: ['.', 'pkgconfig', 'dnsutils', 'collectors', 'loggers', 'transformers', 'netlib', 'processors']
+        package:
+          - '.'
+          - 'pkgconfig'
+          - 'pkglinker'
+          - 'pkgutils'
+          - 'dnsutils'
+          - 'collectors'
+          - 'loggers'
+          - 'transformers'
+          - 'netlib'
+          - 'processors'
         exclude:
           - os-version: macos-latest
             go-version: '1.20'
diff --git a/Makefile b/Makefile
index fc139328..27191b1b 100644
--- a/Makefile
+++ b/Makefile
@@ -50,7 +50,7 @@ dep: check-go
 
 # Builds the project using go build.
 build: check-go
-	CGO_ENABLED=0 go build -v -ldflags="$(LD_FLAGS)" -o ${BINARY_NAME} dnscollector.go multiplexer.go
+	CGO_ENABLED=0 go build -v -ldflags="$(LD_FLAGS)" -o ${BINARY_NAME} dnscollector.go
 
 # Builds and runs the project.
 run: build
@@ -68,7 +68,9 @@ lint:
 tests: check-go
 	@go test -race -cover -v
 	@go test ./pkgconfig/ -race -cover -v
+	@go test ./pkgutils/ -race -cover -v
 	@go test ./dnsutils/ -race -cover -v
+	@go test ./routing/ -race -cover -v
 	@go test ./netlib/ -race -cover -v
 	@go test -timeout 30s ./transformers/ -race -cover -v
 	@go test -timeout 30s ./collectors/ -race -cover -v
diff --git a/README.md b/README.md
index cdb3390b..48459d79 100644
--- a/README.md
+++ b/README.md
@@ -3,8 +3,8 @@
 
 [![Go Report Card](https://goreportcard.com/badge/github.com/dmachard/go-dns-collector)](https://goreportcard.com/report/dmachard/go-dns-collector)
 ![Go version](https://img.shields.io/badge/go%20version-min%201.20-blue)
-![Go tests](https://img.shields.io/badge/go%20tests-370-green)
-![Go lines](https://img.shields.io/badge/go%20lines-32932-red)
+![Go tests](https://img.shields.io/badge/go%20tests-377-green)
+![Go lines](https://img.shields.io/badge/go%20lines-36222-red)
 ![Go Tests](https://github.com/dmachard/go-dns-collector/actions/workflows/testing-go.yml/badge.svg)
 ![Github Actions](https://github.com/dmachard/go-dns-collector/actions/workflows/testing-dnstap.yml/badge.svg)
 ![Github Actions PDNS](https://github.com/dmachard/go-dns-collector/actions/workflows/testing-powerdns.yml/badge.svg)
diff --git a/collectors/dnsmessage.go b/collectors/dnsmessage.go
new file mode 100644
index 00000000..1b58e77a
--- /dev/null
+++ b/collectors/dnsmessage.go
@@ -0,0 +1,311 @@
+package collectors
+
+import (
+	"bufio"
+	"fmt"
+	"net/http"
+	"os"
+	"reflect"
+	"regexp"
+	"strings"
+
+	"github.com/dmachard/go-dnscollector/dnsutils"
+	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
+	"github.com/dmachard/go-dnscollector/transformers"
+	"github.com/dmachard/go-logger"
+)
+
+func isFileSource(matchSource string) bool {
+	return strings.HasPrefix(matchSource, "file://")
+}
+
+func isURLSource(matchSource string) bool {
+	return strings.HasPrefix(matchSource, "http://") || strings.HasPrefix(matchSource, "https://")
+}
+
+type MatchSource struct {
+	regexList  []*regexp.Regexp
+	stringList []string
+}
+
+type DNSMessage struct {
+	doneRun        chan bool
+	doneMonitor    chan bool
+	stopRun        chan bool
+	stopMonitor    chan bool
+	config         *pkgconfig.Config
+	configChan     chan *pkgconfig.Config
+	inputChan      chan dnsutils.DNSMessage
+	logger         *logger.Logger
+	name           string
+	RoutingHandler pkgutils.RoutingHandler
+}
+
+func NewDNSMessage(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *DNSMessage {
+	logger.Info("[%s] collector=dnsmessage - enabled", name)
+	s := &DNSMessage{
+		doneRun:        make(chan bool),
+		doneMonitor:    make(chan bool),
+		stopRun:        make(chan bool),
+		stopMonitor:    make(chan bool),
+		config:         config,
+		configChan:     make(chan *pkgconfig.Config),
+		inputChan:      make(chan dnsutils.DNSMessage, config.Collectors.DNSMessage.ChannelBufferSize),
+		logger:         logger,
+		name:           name,
+		RoutingHandler: pkgutils.NewRoutingHandler(config, logger, name),
+	}
+	s.ReadConfig()
+	return s
+}
+
+func (c *DNSMessage) GetName() string { return c.name }
+
+func (c *DNSMessage) AddDroppedRoute(wrk pkgutils.Worker) {
+	c.RoutingHandler.AddDroppedRoute(wrk)
+}
+
+func (c *DNSMessage) AddDefaultRoute(wrk pkgutils.Worker) {
+	c.RoutingHandler.AddDefaultRoute(wrk)
+}
+
+// deprecated function
+func (c *DNSMessage) SetLoggers(loggers []pkgutils.Worker) {}
+
+// deprecated function
+func (c *DNSMessage) Loggers() ([]chan dnsutils.DNSMessage, []string) {
+	return nil, nil
+}
+
+func (c *DNSMessage) ReadConfigMatching(value interface{}) {
+	reflectedValue := reflect.ValueOf(value)
+	if reflectedValue.Kind() == reflect.Map {
+		keys := reflectedValue.MapKeys()
+		matchSrc := ""
+		srcKind := dnsutils.MatchingKindString
+		for _, k := range keys {
+			v := reflectedValue.MapIndex(k)
+			if k.Interface().(string) == "match-source" {
+				matchSrc = v.Interface().(string)
+			}
+			if k.Interface().(string) == "source-kind" {
+				srcKind = v.Interface().(string)
+			}
+		}
+		if len(matchSrc) > 0 {
+			sourceData, err := c.LoadData(matchSrc, srcKind)
+			if err != nil {
+				c.logger.Fatal(err)
+			}
+			if len(sourceData.regexList) > 0 {
+				value.(map[interface{}]interface{})[srcKind] = sourceData.regexList
+			}
+			if len(sourceData.stringList) > 0 {
+				value.(map[interface{}]interface{})[srcKind] = sourceData.stringList
+			}
+		}
+	}
+}
+
+func (c *DNSMessage) GetInputChannel() chan dnsutils.DNSMessage {
+	return c.inputChan
+}
+
+func (c *DNSMessage) ReadConfig() {
+	// load external file for include
+	if len(c.config.Collectors.DNSMessage.Matching.Include) > 0 {
+		for _, value := range c.config.Collectors.DNSMessage.Matching.Include {
+			c.ReadConfigMatching(value)
+		}
+	}
+	// load external file for exclude
+	if len(c.config.Collectors.DNSMessage.Matching.Exclude) > 0 {
+		for _, value := range c.config.Collectors.DNSMessage.Matching.Exclude {
+			c.ReadConfigMatching(value)
+		}
+	}
+}
+
+func (c *DNSMessage) LoadData(matchSource string, srcKind string) (MatchSource, error) {
+	if isFileSource(matchSource) {
+		dataSource, err := c.LoadFromFile(matchSource, srcKind)
+		if err != nil {
+			c.logger.Fatal(err)
+		}
+		return dataSource, nil
+	} else if isURLSource(matchSource) {
+		dataSource, err := c.LoadFromURL(matchSource, srcKind)
+		if err != nil {
+			c.logger.Fatal(err)
+		}
+		return dataSource, nil
+	}
+	return MatchSource{}, fmt.Errorf("match source not supported %s", matchSource)
+}
+
+func (c *DNSMessage) LoadFromURL(matchSource string, srcKind string) (MatchSource, error) {
+	c.LogInfo("loading matching source from url=%s", matchSource)
+	resp, err := http.Get(matchSource)
+	if err != nil {
+		return MatchSource{}, err
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != http.StatusOK {
+		return MatchSource{}, fmt.Errorf("invalid status code: %d", resp.StatusCode)
+	}
+
+	matchSources := MatchSource{}
+	scanner := bufio.NewScanner(resp.Body)
+
+	switch srcKind {
+	case dnsutils.MatchingKindRegexp:
+		for scanner.Scan() {
+			matchSources.regexList = append(matchSources.regexList, regexp.MustCompile(scanner.Text()))
+		}
+		c.LogInfo("remote source loaded with %d entries kind=%s", len(matchSources.regexList), srcKind)
+	case dnsutils.MatchingKindString:
+		for scanner.Scan() {
+			matchSources.stringList = append(matchSources.stringList, scanner.Text())
+		}
+		c.LogInfo("remote source loaded with %d entries kind=%s", len(matchSources.stringList), srcKind)
+	}
+
+	return matchSources, nil
+}
+
+func (c *DNSMessage) LoadFromFile(filePath string, srcKind string) (MatchSource, error) {
+	localFile := strings.TrimPrefix(filePath, "file://")
+
+	c.LogInfo("loading matching source from file=%s", localFile)
+	file, err := os.Open(localFile)
+	if err != nil {
+		return MatchSource{}, fmt.Errorf("unable to open file: %w", err)
+	}
+
+	matchSources := MatchSource{}
+	scanner := bufio.NewScanner(file)
+
+	switch srcKind {
+	case dnsutils.MatchingKindRegexp:
+		for scanner.Scan() {
+			matchSources.regexList = append(matchSources.regexList, regexp.MustCompile(scanner.Text()))
+		}
+		c.LogInfo("file loaded with %d entries kind=%s", len(matchSources.regexList), srcKind)
+	case dnsutils.MatchingKindString:
+		for scanner.Scan() {
+			matchSources.stringList = append(matchSources.stringList, scanner.Text())
+		}
+		c.LogInfo("file loaded with %d entries kind=%s", len(matchSources.stringList), srcKind)
+	}
+
+	return matchSources, nil
+}
+
+func (c *DNSMessage) ReloadConfig(config *pkgconfig.Config) {
+	c.LogInfo("reload configuration...")
+	c.configChan <- config
+}
+
+func (c *DNSMessage) LogInfo(msg string, v ...interface{}) {
+	c.logger.Info("["+c.name+"] collector=dnsmessage - "+msg, v...)
+}
+
+func (c *DNSMessage) LogError(msg string, v ...interface{}) {
+	c.logger.Error("["+c.name+"] collector=dnsmessage - "+msg, v...)
+}
+
+func (c *DNSMessage) Stop() {
+	c.LogInfo("stopping routing handler...")
+	c.RoutingHandler.Stop()
+
+	// read done channel and block until run is terminated
+	c.LogInfo("stopping run...")
+	c.stopRun <- true
+	<-c.doneRun
+}
+
+func (c *DNSMessage) Run() {
+	c.LogInfo("starting collector...")
+	var err error
+
+	// prepare next channels
+	defaultRoutes, defaultNames := c.RoutingHandler.GetDefaultRoutes()
+	droppedRoutes, droppedNames := c.RoutingHandler.GetDroppedRoutes()
+
+	// prepare transforms
+	subprocessors := transformers.NewTransforms(&c.config.IngoingTransformers, c.logger, c.name, defaultRoutes, 0)
+
+RUN_LOOP:
+	for {
+		select {
+		case <-c.stopRun:
+			c.doneRun <- true
+			break RUN_LOOP
+
+		case cfg := <-c.configChan:
+
+			// save the new config
+			c.config = cfg
+			c.ReadConfig()
+
+		case dm, opened := <-c.inputChan:
+			if !opened {
+				c.LogInfo("channel closed, exit")
+				return
+			}
+
+			// matching enabled, filtering DNS messages ?
+			matched := true
+			matchedInclude := false
+			matchedExclude := false
+
+			if len(c.config.Collectors.DNSMessage.Matching.Include) > 0 {
+				err, matchedInclude = dm.Matching(c.config.Collectors.DNSMessage.Matching.Include)
+				if err != nil {
+					c.LogError(err.Error())
+				}
+				if matched && matchedInclude {
+					matched = true
+				} else {
+					matched = false
+				}
+			}
+
+			if len(c.config.Collectors.DNSMessage.Matching.Exclude) > 0 {
+				err, matchedExclude = dm.Matching(c.config.Collectors.DNSMessage.Matching.Exclude)
+				if err != nil {
+					c.LogError(err.Error())
+				}
+				if matched && !matchedExclude {
+					matched = true
+				} else {
+					matched = false
+				}
+			}
+
+			// apply tranforms on matched packets only
+			// init dns message with additionnals parts if necessary
+			if matched {
+				subprocessors.InitDNSMessageFormat(&dm)
+				if subprocessors.ProcessMessage(&dm) == transformers.ReturnDrop {
+					c.RoutingHandler.SendTo(droppedRoutes, droppedNames, dm)
+					continue
+				}
+			}
+
+			// drop packet ?
+			if !matched {
+				c.RoutingHandler.SendTo(droppedRoutes, droppedNames, dm)
+				continue
+			}
+
+			// send to next
+			c.RoutingHandler.SendTo(defaultRoutes, defaultNames, dm)
+
+		}
+
+	}
+	c.LogInfo("run terminated")
+}
diff --git a/collectors/dnstap.go b/collectors/dnstap.go
index fa9f0994..928ec4b7 100644
--- a/collectors/dnstap.go
+++ b/collectors/dnstap.go
@@ -15,45 +15,47 @@ import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/netlib"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/processors"
 	"github.com/dmachard/go-framestream"
 	"github.com/dmachard/go-logger"
 )
 
 type Dnstap struct {
-	doneRun       chan bool
-	doneMonitor   chan bool
-	stopRun       chan bool
-	stopMonitor   chan bool
-	listen        net.Listener
-	conns         []net.Conn
-	sockPath      string
-	loggers       []dnsutils.Worker
-	config        *pkgconfig.Config
-	configChan    chan *pkgconfig.Config
-	logger        *logger.Logger
-	name          string
-	connMode      string
-	connID        int
-	droppedCount  int
-	dropped       chan int
-	tapProcessors []processors.DNSTapProcessor
+	doneRun          chan bool
+	doneMonitor      chan bool
+	stopRun          chan bool
+	stopMonitor      chan bool
+	listen           net.Listener
+	conns            []net.Conn
+	sockPath         string
+	defaultRoutes    []pkgutils.Worker
+	droppedRoutes    []pkgutils.Worker
+	config           *pkgconfig.Config
+	configChan       chan *pkgconfig.Config
+	logger           *logger.Logger
+	name             string
+	connMode         string
+	connID           int
+	droppedCount     int
+	droppedProcessor chan int
+	tapProcessors    []processors.DNSTapProcessor
 	sync.RWMutex
 }
 
-func NewDnstap(loggers []dnsutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *Dnstap {
+func NewDnstap(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *Dnstap {
 	logger.Info("[%s] collector=dnstap - enabled", name)
 	s := &Dnstap{
-		doneRun:     make(chan bool),
-		doneMonitor: make(chan bool),
-		stopRun:     make(chan bool),
-		stopMonitor: make(chan bool),
-		dropped:     make(chan int),
-		config:      config,
-		configChan:  make(chan *pkgconfig.Config),
-		loggers:     loggers,
-		logger:      logger,
-		name:        name,
+		doneRun:          make(chan bool),
+		doneMonitor:      make(chan bool),
+		stopRun:          make(chan bool),
+		stopMonitor:      make(chan bool),
+		droppedProcessor: make(chan int),
+		config:           config,
+		configChan:       make(chan *pkgconfig.Config),
+		defaultRoutes:    loggers,
+		logger:           logger,
+		name:             name,
 	}
 	s.ReadConfig()
 	return s
@@ -61,18 +63,20 @@ func NewDnstap(loggers []dnsutils.Worker, config *pkgconfig.Config, logger *logg
 
 func (c *Dnstap) GetName() string { return c.name }
 
-func (c *Dnstap) SetLoggers(loggers []dnsutils.Worker) {
-	c.loggers = loggers
+func (c *Dnstap) AddDroppedRoute(wrk pkgutils.Worker) {
+	c.droppedRoutes = append(c.droppedRoutes, wrk)
+}
+
+func (c *Dnstap) AddDefaultRoute(wrk pkgutils.Worker) {
+	c.defaultRoutes = append(c.defaultRoutes, wrk)
+}
+
+func (c *Dnstap) SetLoggers(loggers []pkgutils.Worker) {
+	c.defaultRoutes = loggers
 }
 
 func (c *Dnstap) Loggers() ([]chan dnsutils.DNSMessage, []string) {
-	channels := []chan dnsutils.DNSMessage{}
-	names := []string{}
-	for _, p := range c.loggers {
-		channels = append(channels, p.Channel())
-		names = append(names, p.GetName())
-	}
-	return channels, names
+	return pkgutils.GetRoutes(c.defaultRoutes)
 }
 
 func (c *Dnstap) ReadConfig() {
@@ -127,12 +131,20 @@ func (c *Dnstap) HandleConn(conn net.Conn) {
 	peer := conn.RemoteAddr().String()
 	c.LogConnInfo(connID, "new connection from %s", peer)
 
-	// start dnstap subprocessor
-	dnstapProcessor := processors.NewDNSTapProcessor(connID, c.config, c.logger, c.name, c.config.Collectors.Dnstap.ChannelBufferSize)
+	// start dnstap processor
+	dnstapProcessor := processors.NewDNSTapProcessor(
+		connID,
+		c.config,
+		c.logger,
+		c.name,
+		c.config.Collectors.Dnstap.ChannelBufferSize,
+	)
 	c.Lock()
 	c.tapProcessors = append(c.tapProcessors, dnstapProcessor)
 	c.Unlock()
-	go dnstapProcessor.Run(c.Loggers())
+
+	// run processor
+	go dnstapProcessor.Run(c.defaultRoutes, c.droppedRoutes)
 
 	// frame stream library
 	r := bufio.NewReader(conn)
@@ -191,7 +203,7 @@ func (c *Dnstap) HandleConn(conn net.Conn) {
 		select {
 		case dnstapProcessor.GetChannel() <- frame.Data(): // Successful send to channel
 		default:
-			c.dropped <- 1
+			c.droppedProcessor <- 1
 		}
 	}
 
@@ -216,7 +228,7 @@ func (c *Dnstap) HandleConn(conn net.Conn) {
 	c.LogConnInfo(connID, "connection handler terminated")
 }
 
-func (c *Dnstap) Channel() chan dnsutils.DNSMessage {
+func (c *Dnstap) GetInputChannel() chan dnsutils.DNSMessage {
 	return nil
 }
 
@@ -312,16 +324,16 @@ func (c *Dnstap) MonitorCollector() {
 MONITOR_LOOP:
 	for {
 		select {
-		case <-c.dropped:
+		case <-c.droppedProcessor:
 			c.droppedCount++
 		case <-c.stopMonitor:
-			close(c.dropped)
+			close(c.droppedProcessor)
 			bufferFull.Stop()
 			c.doneMonitor <- true
 			break MONITOR_LOOP
 		case <-bufferFull.C:
 			if c.droppedCount > 0 {
-				c.LogError("recv buffer is full, %d packet(s) dropped", c.droppedCount)
+				c.LogError("processor buffer is full, %d packet(s) dropped", c.droppedCount)
 				c.droppedCount = 0
 			}
 			bufferFull.Reset(watchInterval)
diff --git a/collectors/dnstap_proxifier.go b/collectors/dnstap_proxifier.go
index 4fdc6711..de9e8eb2 100644
--- a/collectors/dnstap_proxifier.go
+++ b/collectors/dnstap_proxifier.go
@@ -11,34 +11,38 @@ import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/netlib"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-framestream"
 	"github.com/dmachard/go-logger"
 )
 
 type DnstapProxifier struct {
-	doneRun    chan bool
-	stopRun    chan bool
-	listen     net.Listener
-	conns      []net.Conn
-	sockPath   string
-	loggers    []dnsutils.Worker
-	config     *pkgconfig.Config
-	configChan chan *pkgconfig.Config
-	logger     *logger.Logger
-	name       string
-	stopping   bool
-}
-
-func NewDnstapProxifier(loggers []dnsutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *DnstapProxifier {
+	doneRun        chan bool
+	stopRun        chan bool
+	listen         net.Listener
+	conns          []net.Conn
+	sockPath       string
+	defaultRoutes  []pkgutils.Worker
+	droppedRoutes  []pkgutils.Worker
+	config         *pkgconfig.Config
+	configChan     chan *pkgconfig.Config
+	logger         *logger.Logger
+	name           string
+	stopping       bool
+	RoutingHandler pkgutils.RoutingHandler
+}
+
+func NewDnstapProxifier(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *DnstapProxifier {
 	logger.Info("[%s] collector=dnstaprelay - enabled", name)
 	s := &DnstapProxifier{
-		doneRun:    make(chan bool),
-		stopRun:    make(chan bool),
-		config:     config,
-		configChan: make(chan *pkgconfig.Config),
-		loggers:    loggers,
-		logger:     logger,
-		name:       name,
+		doneRun:        make(chan bool),
+		stopRun:        make(chan bool),
+		config:         config,
+		configChan:     make(chan *pkgconfig.Config),
+		defaultRoutes:  loggers,
+		logger:         logger,
+		name:           name,
+		RoutingHandler: pkgutils.NewRoutingHandler(config, logger, name),
 	}
 	s.ReadConfig()
 	return s
@@ -46,14 +50,22 @@ func NewDnstapProxifier(loggers []dnsutils.Worker, config *pkgconfig.Config, log
 
 func (c *DnstapProxifier) GetName() string { return c.name }
 
-func (c *DnstapProxifier) SetLoggers(loggers []dnsutils.Worker) {
-	c.loggers = loggers
+func (c *DnstapProxifier) AddDroppedRoute(wrk pkgutils.Worker) {
+	c.droppedRoutes = append(c.droppedRoutes, wrk)
+}
+
+func (c *DnstapProxifier) AddDefaultRoute(wrk pkgutils.Worker) {
+	c.defaultRoutes = append(c.defaultRoutes, wrk)
+}
+
+func (c *DnstapProxifier) SetLoggers(loggers []pkgutils.Worker) {
+	c.defaultRoutes = loggers
 }
 
 func (c *DnstapProxifier) Loggers() []chan dnsutils.DNSMessage {
 	channels := []chan dnsutils.DNSMessage{}
-	for _, p := range c.loggers {
-		channels = append(channels, p.Channel())
+	for _, p := range c.defaultRoutes {
+		channels = append(channels, p.GetInputChannel())
 	}
 	return channels
 }
@@ -130,7 +142,7 @@ func (c *DnstapProxifier) HandleConn(conn net.Conn) {
 	c.LogInfo("%s - connection closed\n", peer)
 }
 
-func (c *DnstapProxifier) Channel() chan dnsutils.DNSMessage {
+func (c *DnstapProxifier) GetInputChannel() chan dnsutils.DNSMessage {
 	return nil
 }
 
diff --git a/collectors/dnstap_proxifier_test.go b/collectors/dnstap_proxifier_test.go
index 9ca81d9c..48a73d38 100644
--- a/collectors/dnstap_proxifier_test.go
+++ b/collectors/dnstap_proxifier_test.go
@@ -7,10 +7,10 @@ import (
 	"testing"
 	"time"
 
-	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/loggers"
 	"github.com/dmachard/go-dnscollector/netlib"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/processors"
 	"github.com/dmachard/go-framestream"
 	"github.com/dmachard/go-logger"
@@ -56,7 +56,7 @@ func Test_DnstapProxifier(t *testing.T) {
 				config.Collectors.DnstapProxifier.SockPath = tc.address
 			}
 
-			c := NewDnstapProxifier([]dnsutils.Worker{g}, config, logger.New(false), "test")
+			c := NewDnstapProxifier([]pkgutils.Worker{g}, config, logger.New(false), "test")
 			if err := c.Listen(); err != nil {
 				log.Fatal("collector dnstap relay error: ", err)
 			}
@@ -100,7 +100,7 @@ func Test_DnstapProxifier(t *testing.T) {
 			}
 
 			// waiting message in channel
-			msg := <-g.Channel()
+			msg := <-g.GetInputChannel()
 			if len(msg.DNSTap.Payload) == 0 {
 				t.Errorf("DNStap payload is empty")
 			}
@@ -117,7 +117,7 @@ func Test_DnstapProxifier(t *testing.T) {
 // 	config.Collectors.DnstapProxifier.ListenPort = 6100
 // 	config.Collectors.DnstapProxifier.SockPath = "/tmp/dnscollector_relay.sock"
 
-// 	c := NewDnstapProxifier([]dnsutils.Worker{g}, config, logger.New(false), "test")
+// 	c := NewDnstapProxifier([]pkgutils.Worker{g}, config, logger.New(false), "test")
 // 	if err := c.Listen(); err != nil {
 // 		log.Fatal("collector dnstap relay tcp listening error: ", err)
 // 	}
@@ -170,7 +170,7 @@ func Test_DnstapProxifier(t *testing.T) {
 // 	g := loggers.NewFakeLogger()
 // 	config := pkgconfig.GetFakeConfig()
 // 	config.Collectors.DnstapProxifier.SockPath = "/tmp/dnscollector_relay.sock"
-// 	c := NewDnstapProxifier([]dnsutils.Worker{g}, config, logger.New(false), "test")
+// 	c := NewDnstapProxifier([]pkgutils.Worker{g}, config, logger.New(false), "test")
 // 	if err := c.Listen(); err != nil {
 // 		log.Fatal("collector dnstap replay unix listening  error: ", err)
 // 	}
diff --git a/collectors/dnstap_test.go b/collectors/dnstap_test.go
index 1998677b..3c790704 100644
--- a/collectors/dnstap_test.go
+++ b/collectors/dnstap_test.go
@@ -9,10 +9,10 @@ import (
 	"testing"
 	"time"
 
-	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/loggers"
 	"github.com/dmachard/go-dnscollector/netlib"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/processors"
 	"github.com/dmachard/go-framestream"
 	"github.com/dmachard/go-logger"
@@ -62,7 +62,7 @@ func Test_DnstapCollector(t *testing.T) {
 				config.Collectors.Dnstap.SockPath = tc.address
 			}
 
-			c := NewDnstap([]dnsutils.Worker{g}, config, logger.New(false), "test")
+			c := NewDnstap([]pkgutils.Worker{g}, config, logger.New(false), "test")
 			if err := c.Listen(); err != nil {
 				log.Fatal("collector listening  error: ", err)
 			}
@@ -106,7 +106,7 @@ func Test_DnstapCollector(t *testing.T) {
 			}
 
 			// waiting message in channel
-			msg := <-g.Channel()
+			msg := <-g.GetInputChannel()
 			if msg.DNSTap.Operation != tc.operation {
 				t.Errorf("want %s, got %s", tc.operation, msg.DNSTap.Operation)
 			}
@@ -129,7 +129,7 @@ func Test_DnstapCollector_CloseFrameStream(t *testing.T) {
 
 	// start the collector in unix mode
 	g := loggers.NewFakeLogger()
-	c := NewDnstap([]dnsutils.Worker{g}, config, lg, "test")
+	c := NewDnstap([]pkgutils.Worker{g}, config, lg, "test")
 	if err := c.Listen(); err != nil {
 		log.Fatal("collector listening  error: ", err)
 	}
diff --git a/collectors/file_ingestor.go b/collectors/file_ingestor.go
index 5207a115..d2b6b697 100644
--- a/collectors/file_ingestor.go
+++ b/collectors/file_ingestor.go
@@ -14,6 +14,7 @@ import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/netlib"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/processors"
 	"github.com/dmachard/go-logger"
 	framestream "github.com/farsightsec/golang-framestream"
@@ -38,7 +39,8 @@ func IsValidMode(mode string) bool {
 type FileIngestor struct {
 	done            chan bool
 	exit            chan bool
-	loggers         []dnsutils.Worker
+	droppedRoutes   []pkgutils.Worker
+	defaultRoutes   []pkgutils.Worker
 	config          *pkgconfig.Config
 	configChan      chan *pkgconfig.Config
 	logger          *logger.Logger
@@ -52,14 +54,14 @@ type FileIngestor struct {
 	mu              sync.Mutex
 }
 
-func NewFileIngestor(loggers []dnsutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *FileIngestor {
+func NewFileIngestor(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *FileIngestor {
 	logger.Info("[%s] collector=fileingestor - enabled", name)
 	s := &FileIngestor{
 		done:          make(chan bool),
 		exit:          make(chan bool),
 		config:        config,
 		configChan:    make(chan *pkgconfig.Config),
-		loggers:       loggers,
+		defaultRoutes: loggers,
 		logger:        logger,
 		name:          name,
 		watcherTimers: make(map[string]*time.Timer),
@@ -70,18 +72,20 @@ func NewFileIngestor(loggers []dnsutils.Worker, config *pkgconfig.Config, logger
 
 func (c *FileIngestor) GetName() string { return c.name }
 
-func (c *FileIngestor) SetLoggers(loggers []dnsutils.Worker) {
-	c.loggers = loggers
+func (c *FileIngestor) AddDroppedRoute(wrk pkgutils.Worker) {
+	c.droppedRoutes = append(c.droppedRoutes, wrk)
+}
+
+func (c *FileIngestor) AddDefaultRoute(wrk pkgutils.Worker) {
+	c.defaultRoutes = append(c.defaultRoutes, wrk)
+}
+
+func (c *FileIngestor) SetLoggers(loggers []pkgutils.Worker) {
+	c.defaultRoutes = loggers
 }
 
 func (c *FileIngestor) Loggers() ([]chan dnsutils.DNSMessage, []string) {
-	channels := []chan dnsutils.DNSMessage{}
-	names := []string{}
-	for _, p := range c.loggers {
-		channels = append(channels, p.Channel())
-		names = append(names, p.GetName())
-	}
-	return channels, names
+	return pkgutils.GetRoutes(c.defaultRoutes)
 }
 
 func (c *FileIngestor) ReadConfig() {
@@ -110,7 +114,7 @@ func (c *FileIngestor) LogError(msg string, v ...interface{}) {
 	c.logger.Error("["+c.name+"] collector=fileingestor - "+msg, v...)
 }
 
-func (c *FileIngestor) Channel() chan dnsutils.DNSMessage {
+func (c *FileIngestor) GetInputChannel() chan dnsutils.DNSMessage {
 	return nil
 }
 
@@ -374,11 +378,17 @@ func (c *FileIngestor) Run() {
 	c.LogInfo("starting collector...")
 
 	c.dnsProcessor = processors.NewDNSProcessor(c.config, c.logger, c.name, c.config.Collectors.FileIngestor.ChannelBufferSize)
-	go c.dnsProcessor.Run(c.Loggers())
+	go c.dnsProcessor.Run(c.defaultRoutes, c.droppedRoutes)
 
 	// start dnstap subprocessor
-	c.dnstapProcessor = processors.NewDNSTapProcessor(0, c.config, c.logger, c.name, c.config.Collectors.FileIngestor.ChannelBufferSize)
-	go c.dnstapProcessor.Run(c.Loggers())
+	c.dnstapProcessor = processors.NewDNSTapProcessor(
+		0,
+		c.config,
+		c.logger,
+		c.name,
+		c.config.Collectors.FileIngestor.ChannelBufferSize,
+	)
+	go c.dnstapProcessor.Run(c.defaultRoutes, c.droppedRoutes)
 
 	// read current folder content
 	entries, err := os.ReadDir(c.config.Collectors.FileIngestor.WatchDir)
diff --git a/collectors/file_ingestor_test.go b/collectors/file_ingestor_test.go
index 26a23b96..52c8e1aa 100644
--- a/collectors/file_ingestor_test.go
+++ b/collectors/file_ingestor_test.go
@@ -6,6 +6,7 @@ import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/loggers"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-logger"
 )
 
@@ -17,13 +18,13 @@ func Test_FileIngestor_Pcap(t *testing.T) {
 	config.Collectors.FileIngestor.WatchDir = "./../testsdata/pcap/"
 
 	// init collector
-	c := NewFileIngestor([]dnsutils.Worker{g}, config, logger.New(false), "test")
+	c := NewFileIngestor([]pkgutils.Worker{g}, config, logger.New(false), "test")
 	go c.Run()
 
 	// waiting message in channel
 	for {
 		// read dns message from channel
-		msg := <-g.Channel()
+		msg := <-g.GetInputChannel()
 
 		// check qname
 		if msg.DNSTap.Operation == dnsutils.DNSTapClientQuery {
diff --git a/collectors/file_tail.go b/collectors/file_tail.go
index 4685273c..cd429b2d 100644
--- a/collectors/file_tail.go
+++ b/collectors/file_tail.go
@@ -11,6 +11,7 @@ import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/netlib"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/transformers"
 	"github.com/dmachard/go-logger"
 	"github.com/hpcloud/tail"
@@ -18,26 +19,26 @@ import (
 )
 
 type Tail struct {
-	doneRun    chan bool
-	stopRun    chan bool
-	tailf      *tail.Tail
-	loggers    []dnsutils.Worker
-	config     *pkgconfig.Config
-	configChan chan *pkgconfig.Config
-	logger     *logger.Logger
-	name       string
+	doneRun       chan bool
+	stopRun       chan bool
+	tailf         *tail.Tail
+	defaultRoutes []pkgutils.Worker
+	config        *pkgconfig.Config
+	configChan    chan *pkgconfig.Config
+	logger        *logger.Logger
+	name          string
 }
 
-func NewTail(loggers []dnsutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *Tail {
+func NewTail(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *Tail {
 	logger.Info("[%s] collector=tail - enabled", name)
 	s := &Tail{
-		doneRun:    make(chan bool),
-		stopRun:    make(chan bool),
-		config:     config,
-		configChan: make(chan *pkgconfig.Config),
-		loggers:    loggers,
-		logger:     logger,
-		name:       name,
+		doneRun:       make(chan bool),
+		stopRun:       make(chan bool),
+		config:        config,
+		configChan:    make(chan *pkgconfig.Config),
+		defaultRoutes: loggers,
+		logger:        logger,
+		name:          name,
 	}
 	s.ReadConfig()
 	return s
@@ -45,14 +46,22 @@ func NewTail(loggers []dnsutils.Worker, config *pkgconfig.Config, logger *logger
 
 func (c *Tail) GetName() string { return c.name }
 
-func (c *Tail) SetLoggers(loggers []dnsutils.Worker) {
-	c.loggers = loggers
+func (c *Tail) AddDroppedRoute(wrk pkgutils.Worker) {
+	// TODO
+}
+
+func (c *Tail) AddDefaultRoute(wrk pkgutils.Worker) {
+	c.defaultRoutes = append(c.defaultRoutes, wrk)
+}
+
+func (c *Tail) SetLoggers(loggers []pkgutils.Worker) {
+	c.defaultRoutes = loggers
 }
 
 func (c *Tail) Loggers() []chan dnsutils.DNSMessage {
 	channels := []chan dnsutils.DNSMessage{}
-	for _, p := range c.loggers {
-		channels = append(channels, p.Channel())
+	for _, p := range c.defaultRoutes {
+		channels = append(channels, p.GetInputChannel())
 	}
 	return channels
 }
@@ -72,7 +81,7 @@ func (c *Tail) LogError(msg string, v ...interface{}) {
 	c.logger.Error("["+c.name+"] collector=tail - "+msg, v...)
 }
 
-func (c *Tail) Channel() chan dnsutils.DNSMessage {
+func (c *Tail) GetInputChannel() chan dnsutils.DNSMessage {
 	return nil
 }
 
diff --git a/collectors/file_tail_test.go b/collectors/file_tail_test.go
index a758ad67..7f276637 100644
--- a/collectors/file_tail_test.go
+++ b/collectors/file_tail_test.go
@@ -7,9 +7,9 @@ import (
 	"testing"
 	"time"
 
-	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/loggers"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-logger"
 )
 
@@ -29,7 +29,7 @@ func TestTailRun(t *testing.T) {
 
 	// init collector
 	g := loggers.NewFakeLogger()
-	c := NewTail([]dnsutils.Worker{g}, config, logger.New(false), "test")
+	c := NewTail([]pkgutils.Worker{g}, config, logger.New(false), "test")
 	if err := c.Follow(); err != nil {
 		t.Errorf("collector tail following error: %e", err)
 	}
@@ -45,7 +45,7 @@ func TestTailRun(t *testing.T) {
 	w.Flush()
 
 	// waiting message in channel
-	msg := <-g.Channel()
+	msg := <-g.GetInputChannel()
 	if msg.DNS.Qname != "www.google.org" {
 		t.Errorf("want www.google.org, got %s", msg.DNS.Qname)
 	}
diff --git a/collectors/powerdns.go b/collectors/powerdns.go
index 57954d8f..50dd694a 100644
--- a/collectors/powerdns.go
+++ b/collectors/powerdns.go
@@ -14,6 +14,7 @@ import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/netlib"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/processors"
 	"github.com/dmachard/go-logger"
 	powerdns_protobuf "github.com/dmachard/go-powerdns-protobuf"
@@ -28,7 +29,8 @@ type ProtobufPowerDNS struct {
 	listen         net.Listener
 	connID         int
 	conns          []net.Conn
-	loggers        []dnsutils.Worker
+	droppedRoutes  []pkgutils.Worker
+	defaultRoutes  []pkgutils.Worker
 	config         *pkgconfig.Config
 	configChan     chan *pkgconfig.Config
 	logger         *logger.Logger
@@ -39,20 +41,20 @@ type ProtobufPowerDNS struct {
 	sync.RWMutex
 }
 
-func NewProtobufPowerDNS(loggers []dnsutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *ProtobufPowerDNS {
+func NewProtobufPowerDNS(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *ProtobufPowerDNS {
 	logger.Info("[%s] pdns collector - enabled", name)
 	s := &ProtobufPowerDNS{
-		doneRun:     make(chan bool),
-		doneMonitor: make(chan bool),
-		stopRun:     make(chan bool),
-		stopMonitor: make(chan bool),
-		cleanup:     make(chan bool),
-		dropped:     make(chan int),
-		config:      config,
-		configChan:  make(chan *pkgconfig.Config),
-		loggers:     loggers,
-		logger:      logger,
-		name:        name,
+		doneRun:       make(chan bool),
+		doneMonitor:   make(chan bool),
+		stopRun:       make(chan bool),
+		stopMonitor:   make(chan bool),
+		cleanup:       make(chan bool),
+		dropped:       make(chan int),
+		config:        config,
+		configChan:    make(chan *pkgconfig.Config),
+		defaultRoutes: loggers,
+		logger:        logger,
+		name:          name,
 	}
 	s.ReadConfig()
 	return s
@@ -60,18 +62,20 @@ func NewProtobufPowerDNS(loggers []dnsutils.Worker, config *pkgconfig.Config, lo
 
 func (c *ProtobufPowerDNS) GetName() string { return c.name }
 
-func (c *ProtobufPowerDNS) SetLoggers(loggers []dnsutils.Worker) {
-	c.loggers = loggers
+func (c *ProtobufPowerDNS) AddDroppedRoute(wrk pkgutils.Worker) {
+	c.droppedRoutes = append(c.droppedRoutes, wrk)
+}
+
+func (c *ProtobufPowerDNS) AddDefaultRoute(wrk pkgutils.Worker) {
+	c.defaultRoutes = append(c.defaultRoutes, wrk)
+}
+
+func (c *ProtobufPowerDNS) SetLoggers(loggers []pkgutils.Worker) {
+	c.defaultRoutes = loggers
 }
 
 func (c *ProtobufPowerDNS) Loggers() ([]chan dnsutils.DNSMessage, []string) {
-	channels := []chan dnsutils.DNSMessage{}
-	names := []string{}
-	for _, p := range c.loggers {
-		channels = append(channels, p.Channel())
-		names = append(names, p.GetName())
-	}
-	return channels, names
+	return pkgutils.GetRoutes(c.defaultRoutes)
 }
 
 func (c *ProtobufPowerDNS) ReadConfig() {
@@ -122,7 +126,7 @@ func (c *ProtobufPowerDNS) HandleConn(conn net.Conn) {
 	c.Lock()
 	c.pdnsProcessors = append(c.pdnsProcessors, &pdnsProc)
 	c.Unlock()
-	go pdnsProc.Run(c.Loggers())
+	go pdnsProc.Run(c.defaultRoutes, c.droppedRoutes)
 
 	r := bufio.NewReader(conn)
 	pbs := powerdns_protobuf.NewProtobufStream(r, conn, 5*time.Second)
@@ -186,7 +190,7 @@ func (c *ProtobufPowerDNS) HandleConn(conn net.Conn) {
 	c.LogConnInfo(connID, "connection handler terminated")
 }
 
-func (c *ProtobufPowerDNS) Channel() chan dnsutils.DNSMessage {
+func (c *ProtobufPowerDNS) GetInputChannel() chan dnsutils.DNSMessage {
 	return nil
 }
 
diff --git a/collectors/powerdns_test.go b/collectors/powerdns_test.go
index bf29452b..aff4196f 100644
--- a/collectors/powerdns_test.go
+++ b/collectors/powerdns_test.go
@@ -5,17 +5,17 @@ import (
 	"net"
 	"testing"
 
-	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/loggers"
 	"github.com/dmachard/go-dnscollector/netlib"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-logger"
 )
 
 func TestPowerDNS_Run(t *testing.T) {
 	g := loggers.NewFakeLogger()
 
-	c := NewProtobufPowerDNS([]dnsutils.Worker{g}, pkgconfig.GetFakeConfig(), logger.New(false), "test")
+	c := NewProtobufPowerDNS([]pkgutils.Worker{g}, pkgconfig.GetFakeConfig(), logger.New(false), "test")
 	if err := c.Listen(); err != nil {
 		log.Fatal("collector powerdns  listening error: ", err)
 	}
diff --git a/collectors/sniffer_afpacket.go b/collectors/sniffer_afpacket.go
index 422706b4..66411bbf 100644
--- a/collectors/sniffer_afpacket.go
+++ b/collectors/sniffer_afpacket.go
@@ -15,6 +15,7 @@ import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/netlib"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/processors"
 	"github.com/dmachard/go-logger"
 	"github.com/google/gopacket"
@@ -149,27 +150,28 @@ func RemoveBpfFilter(fd int) (err error) {
 }
 
 type AfpacketSniffer struct {
-	done       chan bool
-	exit       chan bool
-	fd         int
-	identity   string
-	loggers    []dnsutils.Worker
-	config     *pkgconfig.Config
-	configChan chan *pkgconfig.Config
-	logger     *logger.Logger
-	name       string
+	done          chan bool
+	exit          chan bool
+	fd            int
+	identity      string
+	defaultRoutes []pkgutils.Worker
+	droppedRoutes []pkgutils.Worker
+	config        *pkgconfig.Config
+	configChan    chan *pkgconfig.Config
+	logger        *logger.Logger
+	name          string
 }
 
-func NewAfpacketSniffer(loggers []dnsutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *AfpacketSniffer {
+func NewAfpacketSniffer(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *AfpacketSniffer {
 	logger.Info("[%s] collector=afpacket - enabled", name)
 	s := &AfpacketSniffer{
-		done:       make(chan bool),
-		exit:       make(chan bool),
-		config:     config,
-		configChan: make(chan *pkgconfig.Config),
-		loggers:    loggers,
-		logger:     logger,
-		name:       name,
+		done:          make(chan bool),
+		exit:          make(chan bool),
+		config:        config,
+		configChan:    make(chan *pkgconfig.Config),
+		defaultRoutes: loggers,
+		logger:        logger,
+		name:          name,
 	}
 	s.ReadConfig()
 	return s
@@ -185,18 +187,20 @@ func (c *AfpacketSniffer) LogError(msg string, v ...interface{}) {
 
 func (c *AfpacketSniffer) GetName() string { return c.name }
 
-func (c *AfpacketSniffer) SetLoggers(loggers []dnsutils.Worker) {
-	c.loggers = loggers
+func (c *AfpacketSniffer) AddDroppedRoute(wrk pkgutils.Worker) {
+	c.droppedRoutes = append(c.droppedRoutes, wrk)
+}
+
+func (c *AfpacketSniffer) AddDefaultRoute(wrk pkgutils.Worker) {
+	c.defaultRoutes = append(c.defaultRoutes, wrk)
+}
+
+func (c *AfpacketSniffer) SetLoggers(loggers []pkgutils.Worker) {
+	c.defaultRoutes = loggers
 }
 
 func (c *AfpacketSniffer) Loggers() ([]chan dnsutils.DNSMessage, []string) {
-	channels := []chan dnsutils.DNSMessage{}
-	names := []string{}
-	for _, p := range c.loggers {
-		channels = append(channels, p.Channel())
-		names = append(names, p.GetName())
-	}
-	return channels, names
+	return pkgutils.GetRoutes(c.defaultRoutes)
 }
 
 func (c *AfpacketSniffer) ReadConfig() {
@@ -208,7 +212,7 @@ func (c *AfpacketSniffer) ReloadConfig(config *pkgconfig.Config) {
 	c.configChan <- config
 }
 
-func (c *AfpacketSniffer) Channel() chan dnsutils.DNSMessage {
+func (c *AfpacketSniffer) GetInputChannel() chan dnsutils.DNSMessage {
 	return nil
 }
 
@@ -278,8 +282,13 @@ func (c *AfpacketSniffer) Run() {
 		}
 	}
 
-	dnsProcessor := processors.NewDNSProcessor(c.config, c.logger, c.name, c.config.Collectors.AfpacketLiveCapture.ChannelBufferSize)
-	go dnsProcessor.Run(c.Loggers())
+	dnsProcessor := processors.NewDNSProcessor(
+		c.config,
+		c.logger,
+		c.name,
+		c.config.Collectors.AfpacketLiveCapture.ChannelBufferSize,
+	)
+	go dnsProcessor.Run(c.defaultRoutes, c.droppedRoutes)
 
 	dnsChan := make(chan netlib.DNSPacket)
 	udpChan := make(chan gopacket.Packet)
diff --git a/collectors/sniffer_afpacket_darwin.go b/collectors/sniffer_afpacket_darwin.go
index d0e06c9e..04d06084 100644
--- a/collectors/sniffer_afpacket_darwin.go
+++ b/collectors/sniffer_afpacket_darwin.go
@@ -6,28 +6,29 @@ package collectors
 import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-logger"
 )
 
 type AfpacketSniffer struct {
-	done    chan bool
-	exit    chan bool
-	loggers []dnsutils.Worker
-	config  *pkgconfig.Config
-	logger  *logger.Logger
-	name    string
+	done          chan bool
+	exit          chan bool
+	defaultRoutes []pkgutils.Worker
+	config        *pkgconfig.Config
+	logger        *logger.Logger
+	name          string
 }
 
 // workaround for macos, not yet supported
-func NewAfpacketSniffer(loggers []dnsutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *AfpacketSniffer {
+func NewAfpacketSniffer(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *AfpacketSniffer {
 	logger.Info("[%s] AFPACKET sniffer - enabled", name)
 	s := &AfpacketSniffer{
-		done:    make(chan bool),
-		exit:    make(chan bool),
-		config:  config,
-		loggers: loggers,
-		logger:  logger,
-		name:    name,
+		done:          make(chan bool),
+		exit:          make(chan bool),
+		config:        config,
+		defaultRoutes: loggers,
+		logger:        logger,
+		name:          name,
 	}
 	s.ReadConfig()
 	return s
@@ -35,8 +36,16 @@ func NewAfpacketSniffer(loggers []dnsutils.Worker, config *pkgconfig.Config, log
 
 func (c *AfpacketSniffer) GetName() string { return c.name }
 
-func (c *AfpacketSniffer) SetLoggers(loggers []dnsutils.Worker) {
-	c.loggers = loggers
+func (c *AfpacketSniffer) AddDroppedRoute(wrk pkgutils.Worker) {
+	// TODO
+}
+
+func (c *AfpacketSniffer) AddDefaultRoute(wrk pkgutils.Worker) {
+	c.defaultRoutes = append(c.defaultRoutes, wrk)
+}
+
+func (c *AfpacketSniffer) SetLoggers(loggers []pkgutils.Worker) {
+	c.defaultRoutes = loggers
 }
 
 func (c *AfpacketSniffer) LogInfo(msg string, v ...interface{}) {
@@ -47,19 +56,15 @@ func (c *AfpacketSniffer) LogError(msg string, v ...interface{}) {
 	c.logger.Error("["+c.name+"] collector dns sniffer - "+msg, v...)
 }
 
-func (c *AfpacketSniffer) Loggers() []chan dnsutils.DNSMessage {
-	channels := []chan dnsutils.DNSMessage{}
-	for _, p := range c.loggers {
-		channels = append(channels, p.Channel())
-	}
-	return channels
+func (c *AfpacketSniffer) Loggers() ([]chan dnsutils.DNSMessage, []string) {
+	return pkgutils.GetRoutes(c.defaultRoutes)
 }
 
 func (c *AfpacketSniffer) ReadConfig() {}
 
 func (c *AfpacketSniffer) ReloadConfig(config *pkgconfig.Config) {}
 
-func (c *AfpacketSniffer) Channel() chan dnsutils.DNSMessage {
+func (c *AfpacketSniffer) GetInputChannel() chan dnsutils.DNSMessage {
 	return nil
 }
 
diff --git a/collectors/sniffer_afpacket_freebsd.go b/collectors/sniffer_afpacket_freebsd.go
index d3c70271..d5850333 100644
--- a/collectors/sniffer_afpacket_freebsd.go
+++ b/collectors/sniffer_afpacket_freebsd.go
@@ -6,28 +6,29 @@ package collectors
 import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-logger"
 )
 
 type AfpacketSniffer struct {
-	done    chan bool
-	exit    chan bool
-	loggers []dnsutils.Worker
-	config  *pkgconfig.Config
-	logger  *logger.Logger
-	name    string
+	done          chan bool
+	exit          chan bool
+	defaultRoutes []pkgutils.Worker
+	config        *pkgconfig.Config
+	logger        *logger.Logger
+	name          string
 }
 
 // workaround for freebsd, not yet supported
-func NewAfpacketSniffer(loggers []dnsutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *AfpacketSniffer {
+func NewAfpacketSniffer(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *AfpacketSniffer {
 	logger.Info("[%s] AFPACKET sniffer - enabled", name)
 	s := &AfpacketSniffer{
-		done:    make(chan bool),
-		exit:    make(chan bool),
-		config:  config,
-		loggers: loggers,
-		logger:  logger,
-		name:    name,
+		done:          make(chan bool),
+		exit:          make(chan bool),
+		config:        config,
+		defaultRoutes: loggers,
+		logger:        logger,
+		name:          name,
 	}
 	s.ReadConfig()
 	return s
@@ -35,8 +36,16 @@ func NewAfpacketSniffer(loggers []dnsutils.Worker, config *pkgconfig.Config, log
 
 func (c *AfpacketSniffer) GetName() string { return c.name }
 
-func (c *AfpacketSniffer) SetLoggers(loggers []dnsutils.Worker) {
-	c.loggers = loggers
+func (c *AfpacketSniffer) AddDroppedRoute(wrk pkgutils.Worker) {
+	// TODO
+}
+
+func (c *AfpacketSniffer) AddDefaultRoute(wrk pkgutils.Worker) {
+	c.defaultRoutes = append(c.defaultRoutes, wrk)
+}
+
+func (c *AfpacketSniffer) SetLoggers(loggers []pkgutils.Worker) {
+	c.defaultRoutes = loggers
 }
 
 func (c *AfpacketSniffer) LogInfo(msg string, v ...interface{}) {
@@ -47,19 +56,15 @@ func (c *AfpacketSniffer) LogError(msg string, v ...interface{}) {
 	c.logger.Error("["+c.name+"] AFPACKET sniffer - "+msg, v...)
 }
 
-func (c *AfpacketSniffer) Loggers() []chan dnsutils.DNSMessage {
-	channels := []chan dnsutils.DNSMessage{}
-	for _, p := range c.loggers {
-		channels = append(channels, p.Channel())
-	}
-	return channels
+func (c *AfpacketSniffer) Loggers() ([]chan dnsutils.DNSMessage, []string) {
+	return pkgutils.GetRoutes(c.defaultRoutes)
 }
 
 func (c *AfpacketSniffer) ReadConfig() {}
 
 func (c *AfpacketSniffer) ReloadConfig(config *pkgconfig.Config) {}
 
-func (c *AfpacketSniffer) Channel() chan dnsutils.DNSMessage {
+func (c *AfpacketSniffer) GetInputChannel() chan dnsutils.DNSMessage {
 	return nil
 }
 
diff --git a/collectors/sniffer_afpacket_test.go b/collectors/sniffer_afpacket_test.go
index 11d34217..0a938bf8 100644
--- a/collectors/sniffer_afpacket_test.go
+++ b/collectors/sniffer_afpacket_test.go
@@ -11,12 +11,13 @@ import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/loggers"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-logger"
 )
 
 func TestAfpacketSnifferRun(t *testing.T) {
 	g := loggers.NewFakeLogger()
-	c := NewAfpacketSniffer([]dnsutils.Worker{g}, pkgconfig.GetFakeConfig(), logger.New(false), "test")
+	c := NewAfpacketSniffer([]pkgutils.Worker{g}, pkgconfig.GetFakeConfig(), logger.New(false), "test")
 	if err := c.Listen(); err != nil {
 		log.Fatal("collector sniffer listening error: ", err)
 	}
@@ -27,7 +28,7 @@ func TestAfpacketSnifferRun(t *testing.T) {
 
 	// waiting message in channel
 	for {
-		msg := <-g.Channel()
+		msg := <-g.GetInputChannel()
 		if msg.DNSTap.Operation == dnsutils.DNSTapClientQuery && msg.DNS.Qname == "dns.collector" {
 			break
 		}
diff --git a/collectors/sniffer_afpacket_windows.go b/collectors/sniffer_afpacket_windows.go
index 19c300ed..d24b8606 100644
--- a/collectors/sniffer_afpacket_windows.go
+++ b/collectors/sniffer_afpacket_windows.go
@@ -6,28 +6,29 @@ package collectors
 import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-logger"
 )
 
 type AfpacketSniffer struct {
-	done    chan bool
-	exit    chan bool
-	loggers []dnsutils.Worker
-	config  *pkgconfig.Config
-	logger  *logger.Logger
-	name    string
+	done          chan bool
+	exit          chan bool
+	defaultRoutes []pkgutils.Worker
+	config        *pkgconfig.Config
+	logger        *logger.Logger
+	name          string
 }
 
 // workaround for macos, not yet supported
-func NewAfpacketSniffer(loggers []dnsutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *AfpacketSniffer {
+func NewAfpacketSniffer(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *AfpacketSniffer {
 	logger.Info("[%s] AFPACKET sniffer - enabled", name)
 	s := &AfpacketSniffer{
-		done:    make(chan bool),
-		exit:    make(chan bool),
-		config:  config,
-		loggers: loggers,
-		logger:  logger,
-		name:    name,
+		done:          make(chan bool),
+		exit:          make(chan bool),
+		config:        config,
+		defaultRoutes: loggers,
+		logger:        logger,
+		name:          name,
 	}
 	s.ReadConfig()
 	return s
@@ -35,8 +36,16 @@ func NewAfpacketSniffer(loggers []dnsutils.Worker, config *pkgconfig.Config, log
 
 func (c *AfpacketSniffer) GetName() string { return c.name }
 
-func (c *AfpacketSniffer) SetLoggers(loggers []dnsutils.Worker) {
-	c.loggers = loggers
+func (c *AfpacketSniffer) AddDroppedRoute(wrk pkgutils.Worker) {
+	// TODO
+}
+
+func (c *AfpacketSniffer) AddDefaultRoute(wrk pkgutils.Worker) {
+	c.defaultRoutes = append(c.defaultRoutes, wrk)
+}
+
+func (c *AfpacketSniffer) SetLoggers(loggers []pkgutils.Worker) {
+	c.defaultRoutes = loggers
 }
 
 func (c *AfpacketSniffer) LogInfo(msg string, v ...interface{}) {
@@ -47,19 +56,15 @@ func (c *AfpacketSniffer) LogError(msg string, v ...interface{}) {
 	c.logger.Error("["+c.name+"] AFPACKET sniffer - "+msg, v...)
 }
 
-func (c *AfpacketSniffer) Loggers() []chan dnsutils.DNSMessage {
-	channels := []chan dnsutils.DNSMessage{}
-	for _, p := range c.loggers {
-		channels = append(channels, p.Channel())
-	}
-	return channels
+func (c *AfpacketSniffer) Loggers() ([]chan dnsutils.DNSMessage, []string) {
+	return pkgutils.GetRoutes(c.defaultRoutes)
 }
 
 func (c *AfpacketSniffer) ReadConfig() {}
 
 func (c *AfpacketSniffer) ReloadConfig(config *pkgconfig.Config) {}
 
-func (c *AfpacketSniffer) Channel() chan dnsutils.DNSMessage {
+func (c *AfpacketSniffer) GetInputChannel() chan dnsutils.DNSMessage {
 	return nil
 }
 
diff --git a/collectors/sniffer_xdp.go b/collectors/sniffer_xdp.go
index c8baf7b8..cb5e9771 100644
--- a/collectors/sniffer_xdp.go
+++ b/collectors/sniffer_xdp.go
@@ -16,6 +16,7 @@ import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/netlib"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/processors"
 	"github.com/dmachard/go-dnscollector/xdp"
 	"github.com/dmachard/go-logger"
@@ -42,26 +43,27 @@ func ConvertIP6(ip [4]uint32) net.IP {
 }
 
 type XDPSniffer struct {
-	done       chan bool
-	exit       chan bool
-	identity   string
-	loggers    []dnsutils.Worker
-	config     *pkgconfig.Config
-	configChan chan *pkgconfig.Config
-	logger     *logger.Logger
-	name       string
+	done          chan bool
+	exit          chan bool
+	identity      string
+	defaultRoutes []pkgutils.Worker
+	droppedRoutes []pkgutils.Worker
+	config        *pkgconfig.Config
+	configChan    chan *pkgconfig.Config
+	logger        *logger.Logger
+	name          string
 }
 
-func NewXDPSniffer(loggers []dnsutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *XDPSniffer {
+func NewXDPSniffer(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *XDPSniffer {
 	logger.Info("[%s] collector=xdp - enabled", name)
 	s := &XDPSniffer{
-		done:       make(chan bool),
-		exit:       make(chan bool),
-		config:     config,
-		configChan: make(chan *pkgconfig.Config),
-		loggers:    loggers,
-		logger:     logger,
-		name:       name,
+		done:          make(chan bool),
+		exit:          make(chan bool),
+		config:        config,
+		configChan:    make(chan *pkgconfig.Config),
+		defaultRoutes: loggers,
+		logger:        logger,
+		name:          name,
 	}
 	s.ReadConfig()
 	return s
@@ -77,18 +79,20 @@ func (c *XDPSniffer) LogError(msg string, v ...interface{}) {
 
 func (c *XDPSniffer) GetName() string { return c.name }
 
-func (c *XDPSniffer) SetLoggers(loggers []dnsutils.Worker) {
-	c.loggers = loggers
+func (c *XDPSniffer) AddDroppedRoute(wrk pkgutils.Worker) {
+	c.droppedRoutes = append(c.droppedRoutes, wrk)
+}
+
+func (c *XDPSniffer) AddDefaultRoute(wrk pkgutils.Worker) {
+	c.defaultRoutes = append(c.defaultRoutes, wrk)
+}
+
+func (c *XDPSniffer) SetLoggers(loggers []pkgutils.Worker) {
+	c.defaultRoutes = loggers
 }
 
 func (c *XDPSniffer) Loggers() ([]chan dnsutils.DNSMessage, []string) {
-	channels := []chan dnsutils.DNSMessage{}
-	names := []string{}
-	for _, p := range c.loggers {
-		channels = append(channels, p.Channel())
-		names = append(names, p.GetName())
-	}
-	return channels, names
+	return pkgutils.GetRoutes(c.defaultRoutes)
 }
 
 func (c *XDPSniffer) ReadConfig() {
@@ -100,7 +104,7 @@ func (c *XDPSniffer) ReloadConfig(config *pkgconfig.Config) {
 	c.configChan <- config
 }
 
-func (c *XDPSniffer) Channel() chan dnsutils.DNSMessage {
+func (c *XDPSniffer) GetInputChannel() chan dnsutils.DNSMessage {
 	return nil
 }
 
@@ -118,8 +122,13 @@ func (c *XDPSniffer) Stop() {
 func (c *XDPSniffer) Run() {
 	c.LogInfo("starting collector...")
 
-	dnsProcessor := processors.NewDNSProcessor(c.config, c.logger, c.name, c.config.Collectors.XdpLiveCapture.ChannelBufferSize)
-	go dnsProcessor.Run(c.Loggers())
+	dnsProcessor := processors.NewDNSProcessor(
+		c.config,
+		c.logger,
+		c.name,
+		c.config.Collectors.XdpLiveCapture.ChannelBufferSize,
+	)
+	go dnsProcessor.Run(c.defaultRoutes, c.droppedRoutes)
 
 	iface, err := net.InterfaceByName(c.config.Collectors.XdpLiveCapture.Device)
 	if err != nil {
diff --git a/collectors/sniffer_xdp_windows.go b/collectors/sniffer_xdp_windows.go
index b7118f88..492eb9db 100644
--- a/collectors/sniffer_xdp_windows.go
+++ b/collectors/sniffer_xdp_windows.go
@@ -6,28 +6,29 @@ package collectors
 import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-logger"
 )
 
 type XDPSniffer struct {
-	done     chan bool
-	exit     chan bool
-	identity string
-	loggers  []dnsutils.Worker
-	config   *pkgconfig.Config
-	logger   *logger.Logger
-	name     string
+	done          chan bool
+	exit          chan bool
+	identity      string
+	defaultRoutes []pkgutils.Worker
+	config        *pkgconfig.Config
+	logger        *logger.Logger
+	name          string
 }
 
-func NewXDPSniffer(loggers []dnsutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *XDPSniffer {
+func NewXDPSniffer(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *XDPSniffer {
 	logger.Info("[%s] XDP collector enabled", name)
 	s := &XDPSniffer{
-		done:    make(chan bool),
-		exit:    make(chan bool),
-		config:  config,
-		loggers: loggers,
-		logger:  logger,
-		name:    name,
+		done:          make(chan bool),
+		exit:          make(chan bool),
+		config:        config,
+		defaultRoutes: loggers,
+		logger:        logger,
+		name:          name,
 	}
 	s.ReadConfig()
 	return s
@@ -43,23 +44,27 @@ func (c *XDPSniffer) LogError(msg string, v ...interface{}) {
 
 func (c *XDPSniffer) GetName() string { return c.name }
 
-func (c *XDPSniffer) SetLoggers(loggers []dnsutils.Worker) {
-	c.loggers = loggers
+func (c *XDPSniffer) AddDroppedRoute(wrk pkgutils.Worker) {
+	// TODO
 }
 
-func (c *XDPSniffer) Loggers() []chan dnsutils.DNSMessage {
-	channels := []chan dnsutils.DNSMessage{}
-	for _, p := range c.loggers {
-		channels = append(channels, p.Channel())
-	}
-	return channels
+func (c *XDPSniffer) AddDefaultRoute(wrk pkgutils.Worker) {
+	c.defaultRoutes = append(c.defaultRoutes, wrk)
+}
+
+func (c *XDPSniffer) SetLoggers(loggers []pkgutils.Worker) {
+	c.defaultRoutes = loggers
+}
+
+func (c *XDPSniffer) Loggers() ([]chan dnsutils.DNSMessage, []string) {
+	return pkgutils.GetRoutes(c.defaultRoutes)
 }
 
 func (c *XDPSniffer) ReadConfig() {}
 
 func (c *XDPSniffer) ReloadConfig(config *pkgconfig.Config) {}
 
-func (c *XDPSniffer) Channel() chan dnsutils.DNSMessage {
+func (c *XDPSniffer) GetInputChannel() chan dnsutils.DNSMessage {
 	return nil
 }
 
diff --git a/collectors/tzsp.go b/collectors/tzsp.go
index 91bf836e..cea1a6cd 100644
--- a/collectors/tzsp.go
+++ b/collectors/tzsp.go
@@ -14,6 +14,7 @@ import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/netlib"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/processors"
 	"github.com/dmachard/go-logger"
 	"github.com/google/gopacket"
@@ -22,25 +23,26 @@ import (
 )
 
 type TZSPSniffer struct {
-	done     chan bool
-	exit     chan bool
-	listen   net.UDPConn
-	loggers  []dnsutils.Worker
-	config   *pkgconfig.Config
-	logger   *logger.Logger
-	name     string
-	identity string
+	done          chan bool
+	exit          chan bool
+	listen        net.UDPConn
+	defaultRoutes []pkgutils.Worker
+	droppedRoutes []pkgutils.Worker
+	config        *pkgconfig.Config
+	logger        *logger.Logger
+	name          string
+	identity      string
 }
 
-func NewTZSP(loggers []dnsutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *TZSPSniffer {
+func NewTZSP(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *TZSPSniffer {
 	logger.Info("[%s] collector=tzsp - enabled", name)
 	s := &TZSPSniffer{
-		done:    make(chan bool),
-		exit:    make(chan bool),
-		config:  config,
-		loggers: loggers,
-		logger:  logger,
-		name:    name,
+		done:          make(chan bool),
+		exit:          make(chan bool),
+		config:        config,
+		defaultRoutes: loggers,
+		logger:        logger,
+		name:          name,
 	}
 	s.ReadConfig()
 	return s
@@ -48,18 +50,20 @@ func NewTZSP(loggers []dnsutils.Worker, config *pkgconfig.Config, logger *logger
 
 func (c *TZSPSniffer) GetName() string { return c.name }
 
-func (c *TZSPSniffer) SetLoggers(loggers []dnsutils.Worker) {
-	c.loggers = loggers
+func (c *TZSPSniffer) AddDroppedRoute(wrk pkgutils.Worker) {
+	c.droppedRoutes = append(c.droppedRoutes, wrk)
+}
+
+func (c *TZSPSniffer) AddDefaultRoute(wrk pkgutils.Worker) {
+	c.defaultRoutes = append(c.defaultRoutes, wrk)
+}
+
+func (c *TZSPSniffer) SetLoggers(loggers []pkgutils.Worker) {
+	c.defaultRoutes = loggers
 }
 
 func (c *TZSPSniffer) Loggers() ([]chan dnsutils.DNSMessage, []string) {
-	channels := []chan dnsutils.DNSMessage{}
-	names := []string{}
-	for _, p := range c.loggers {
-		channels = append(channels, p.Channel())
-		names = append(names, p.GetName())
-	}
-	return channels, names
+	return pkgutils.GetRoutes(c.defaultRoutes)
 }
 
 func (c *TZSPSniffer) LogInfo(msg string, v ...interface{}) {
@@ -106,7 +110,7 @@ func (c *TZSPSniffer) Listen() error {
 	return nil
 }
 
-func (c *TZSPSniffer) Channel() chan dnsutils.DNSMessage {
+func (c *TZSPSniffer) GetInputChannel() chan dnsutils.DNSMessage {
 	return nil
 }
 
@@ -129,7 +133,7 @@ func (c *TZSPSniffer) Run() {
 	}
 
 	dnsProcessor := processors.NewDNSProcessor(c.config, c.logger, c.name, c.config.Collectors.Tzsp.ChannelBufferSize)
-	go dnsProcessor.Run(c.Loggers())
+	go dnsProcessor.Run(c.defaultRoutes, c.droppedRoutes)
 
 	go func() {
 		buf := make([]byte, 65536)
diff --git a/collectors/tzsp_darwin.go b/collectors/tzsp_darwin.go
index 934ebc39..ccfe88d1 100644
--- a/collectors/tzsp_darwin.go
+++ b/collectors/tzsp_darwin.go
@@ -6,28 +6,29 @@ package collectors
 import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-logger"
 )
 
 type TZSPSniffer struct {
-	done    chan bool
-	exit    chan bool
-	loggers []dnsutils.Worker
-	config  *pkgconfig.Config
-	logger  *logger.Logger
-	name    string
+	done          chan bool
+	exit          chan bool
+	defaultRoutes []pkgutils.Worker
+	config        *pkgconfig.Config
+	logger        *logger.Logger
+	name          string
 }
 
 // workaround for macos, not yet supported
-func NewTZSP(loggers []dnsutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *AfpacketSniffer {
+func NewTZSP(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *TZSPSniffer {
 	logger.Info("[%s] tzsp collector - enabled", name)
-	s := &AfpacketSniffer{
-		done:    make(chan bool),
-		exit:    make(chan bool),
-		config:  config,
-		loggers: loggers,
-		logger:  logger,
-		name:    name,
+	s := &TZSPSniffer{
+		done:          make(chan bool),
+		exit:          make(chan bool),
+		config:        config,
+		defaultRoutes: loggers,
+		logger:        logger,
+		name:          name,
 	}
 	s.ReadConfig()
 	return s
@@ -35,8 +36,16 @@ func NewTZSP(loggers []dnsutils.Worker, config *pkgconfig.Config, logger *logger
 
 func (c *TZSPSniffer) GetName() string { return c.name }
 
-func (c *TZSPSniffer) SetLoggers(loggers []dnsutils.Worker) {
-	c.loggers = loggers
+func (c *TZSPSniffer) AddDroppedRoute(wrk pkgutils.Worker) {
+	// TODO
+}
+
+func (c *TZSPSniffer) AddDefaultRoute(wrk pkgutils.Worker) {
+	c.defaultRoutes = append(c.defaultRoutes, wrk)
+}
+
+func (c *TZSPSniffer) SetLoggers(loggers []pkgutils.Worker) {
+	c.defaultRoutes = loggers
 }
 
 func (c *TZSPSniffer) LogInfo(msg string, v ...interface{}) {
@@ -47,19 +56,15 @@ func (c *TZSPSniffer) LogError(msg string, v ...interface{}) {
 	c.logger.Error("["+c.name+"] tzsp collector - "+msg, v...)
 }
 
-func (c *TZSPSniffer) Loggers() []chan dnsutils.DNSMessage {
-	channels := []chan dnsutils.DNSMessage{}
-	for _, p := range c.loggers {
-		channels = append(channels, p.Channel())
-	}
-	return channels
+func (c *TZSPSniffer) Loggers() ([]chan dnsutils.DNSMessage, []string) {
+	return pkgutils.GetRoutes(c.defaultRoutes)
 }
 
 func (c *TZSPSniffer) ReadConfig() {}
 
 func (c *TZSPSniffer) ReloadConfig(config *pkgconfig.Config) {}
 
-func (c *TZSPSniffer) Channel() chan dnsutils.DNSMessage {
+func (c *TZSPSniffer) GetInputChannel() chan dnsutils.DNSMessage {
 	return nil
 }
 
diff --git a/collectors/tzsp_freebsd.go b/collectors/tzsp_freebsd.go
index a122b584..b047a725 100644
--- a/collectors/tzsp_freebsd.go
+++ b/collectors/tzsp_freebsd.go
@@ -6,28 +6,29 @@ package collectors
 import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-logger"
 )
 
 type TZSPSniffer struct {
-	done    chan bool
-	exit    chan bool
-	loggers []dnsutils.Worker
-	config  *pkgconfig.Config
-	logger  *logger.Logger
-	name    string
+	done          chan bool
+	exit          chan bool
+	defaultRoutes []pkgutils.Worker
+	config        *pkgconfig.Config
+	logger        *logger.Logger
+	name          string
 }
 
 // workaround for macos, not yet supported
-func NewTZSP(loggers []dnsutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *AfpacketSniffer {
+func NewTZSP(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *TZSPSniffer {
 	logger.Info("[%s] tzsp collector - enabled", name)
-	s := &AfpacketSniffer{
-		done:    make(chan bool),
-		exit:    make(chan bool),
-		config:  config,
-		loggers: loggers,
-		logger:  logger,
-		name:    name,
+	s := &TZSPSniffer{
+		done:          make(chan bool),
+		exit:          make(chan bool),
+		config:        config,
+		defaultRoutes: loggers,
+		logger:        logger,
+		name:          name,
 	}
 	s.ReadConfig()
 	return s
@@ -35,8 +36,16 @@ func NewTZSP(loggers []dnsutils.Worker, config *pkgconfig.Config, logger *logger
 
 func (c *TZSPSniffer) GetName() string { return c.name }
 
-func (c *TZSPSniffer) SetLoggers(loggers []dnsutils.Worker) {
-	c.loggers = loggers
+func (c *TZSPSniffer) AddDroppedRoute(wrk pkgutils.Worker) {
+	// TODO
+}
+
+func (c *TZSPSniffer) AddDefaultRoute(wrk pkgutils.Worker) {
+	c.defaultRoutes = append(c.defaultRoutes, wrk)
+}
+
+func (c *TZSPSniffer) SetLoggers(loggers []pkgutils.Worker) {
+	c.defaultRoutes = loggers
 }
 
 func (c *TZSPSniffer) LogInfo(msg string, v ...interface{}) {
@@ -47,19 +56,15 @@ func (c *TZSPSniffer) LogError(msg string, v ...interface{}) {
 	c.logger.Error("["+c.name+"] tzsp collector - "+msg, v...)
 }
 
-func (c *TZSPSniffer) Loggers() []chan dnsutils.DNSMessage {
-	channels := []chan dnsutils.DNSMessage{}
-	for _, p := range c.loggers {
-		channels = append(channels, p.Channel())
-	}
-	return channels
+func (c *TZSPSniffer) Loggers() ([]chan dnsutils.DNSMessage, []string) {
+	return pkgutils.GetRoutes(c.defaultRoutes)
 }
 
 func (c *TZSPSniffer) ReadConfig() {}
 
 func (c *TZSPSniffer) ReloadConfig(config *pkgconfig.Config) {}
 
-func (c *TZSPSniffer) Channel() chan dnsutils.DNSMessage {
+func (c *TZSPSniffer) GetInputChannel() chan dnsutils.DNSMessage {
 	return nil
 }
 
diff --git a/collectors/tzsp_windows.go b/collectors/tzsp_windows.go
index da9e24ab..9225c822 100644
--- a/collectors/tzsp_windows.go
+++ b/collectors/tzsp_windows.go
@@ -6,28 +6,29 @@ package collectors
 import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-logger"
 )
 
 type TZSPSniffer struct {
-	done    chan bool
-	exit    chan bool
-	loggers []dnsutils.Worker
-	config  *pkgconfig.Config
-	logger  *logger.Logger
-	name    string
+	done          chan bool
+	exit          chan bool
+	defaultRoutes []pkgutils.Worker
+	config        *pkgconfig.Config
+	logger        *logger.Logger
+	name          string
 }
 
 // workaround for macos, not yet supported
-func NewTZSP(loggers []dnsutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *AfpacketSniffer {
+func NewTZSP(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *TZSPSniffer {
 	logger.Info("[%s] tzsp collector - enabled", name)
-	s := &AfpacketSniffer{
-		done:    make(chan bool),
-		exit:    make(chan bool),
-		config:  config,
-		loggers: loggers,
-		logger:  logger,
-		name:    name,
+	s := &TZSPSniffer{
+		done:          make(chan bool),
+		exit:          make(chan bool),
+		config:        config,
+		defaultRoutes: loggers,
+		logger:        logger,
+		name:          name,
 	}
 	s.ReadConfig()
 	return s
@@ -35,8 +36,16 @@ func NewTZSP(loggers []dnsutils.Worker, config *pkgconfig.Config, logger *logger
 
 func (c *TZSPSniffer) GetName() string { return c.name }
 
-func (c *TZSPSniffer) SetLoggers(loggers []dnsutils.Worker) {
-	c.loggers = loggers
+func (c *TZSPSniffer) AddDroppedRoute(wrk pkgutils.Worker) {
+	// TODO
+}
+
+func (c *TZSPSniffer) AddDefaultRoute(wrk pkgutils.Worker) {
+	c.defaultRoutes = append(c.defaultRoutes, wrk)
+}
+
+func (c *TZSPSniffer) SetLoggers(loggers []pkgutils.Worker) {
+	c.defaultRoutes = loggers
 }
 
 func (c *TZSPSniffer) LogInfo(msg string, v ...interface{}) {
@@ -47,19 +56,15 @@ func (c *TZSPSniffer) LogError(msg string, v ...interface{}) {
 	c.logger.Error("["+c.name+"] tzsp collector - "+msg, v...)
 }
 
-func (c *TZSPSniffer) Loggers() []chan dnsutils.DNSMessage {
-	channels := []chan dnsutils.DNSMessage{}
-	for _, p := range c.loggers {
-		channels = append(channels, p.Channel())
-	}
-	return channels
+func (c *TZSPSniffer) Loggers() ([]chan dnsutils.DNSMessage, []string) {
+	return pkgutils.GetRoutes(c.defaultRoutes)
 }
 
 func (c *TZSPSniffer) ReadConfig() {}
 
 func (c *TZSPSniffer) ReloadConfig(config *pkgconfig.Config) {}
 
-func (c *TZSPSniffer) Channel() chan dnsutils.DNSMessage {
+func (c *TZSPSniffer) GetInputChannel() chan dnsutils.DNSMessage {
 	return nil
 }
 
diff --git a/config.yml b/config.yml
index 3a7eb913..fbe356e9 100644
--- a/config.yml
+++ b/config.yml
@@ -82,6 +82,38 @@ multiplexer:
     - from: [ tap ]
       to: [ console ]
 
+# /!\ experimental,  pipelling running mode /!\
+# pipelines:
+#   - name: main-input
+#     dnstap:
+#       listen-ip: 0.0.0.0
+#       listen-port: 6000
+#     routing-policy:
+#       default: [ filter ]
+
+#   - name: filter
+#     dnsmessage:
+#       matching:
+#         include:
+#           dns.qname: "^.*\\.google\\.com$"
+#     transforms:
+#       atags:
+#         tags: [ "google"]
+#     routing-policy:
+#       dropped: [ outputfile ]
+#       default: [ console ]
+
+#   - name: console
+#     stdout:
+#       mode: text
+
+#   - name: outputfile
+#     logfile:
+#       file-path:  "/tmp/dnstap.log"
+#       max-size: 1000
+#       max-files: 10
+#       mode: flat-json
+
 ################################################
 # list of supported collectors
 ################################################
diff --git a/dnscollector.go b/dnscollector.go
index 93340959..77aef849 100644
--- a/dnscollector.go
+++ b/dnscollector.go
@@ -9,6 +9,8 @@ import (
 
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkglinker"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-logger"
 	"github.com/natefinch/lumberjack"
 	"github.com/prometheus/common/version"
@@ -86,8 +88,14 @@ func main() {
 	// create logger
 	logger := logger.New(true)
 
-	// load config
-	config, err := pkgconfig.LoadConfig(configPath)
+	// get DNSMessage flat model
+	dmRef, err := dnsutils.GetFlatDNSMessage()
+	if err != nil {
+		fmt.Printf("config error: unable to get DNSMessage reference %v\n", err)
+		os.Exit(1)
+	}
+
+	config, err := pkgconfig.LoadConfig(configPath, dmRef)
 	if err != nil {
 		fmt.Printf("config error: %v\n", err)
 		os.Exit(1)
@@ -99,13 +107,24 @@ func main() {
 	logger.Info("main - starting dns-collector...")
 
 	// init active collectors and loggers
-	mapLoggers := make(map[string]dnsutils.Worker)
-	mapCollectors := make(map[string]dnsutils.Worker)
+	mapLoggers := make(map[string]pkgutils.Worker)
+	mapCollectors := make(map[string]pkgutils.Worker)
 
-	// enable multiplexer mode
-	if IsMuxEnabled(config) {
+	// running mode,
+	// multiplexer ?
+	if pkglinker.IsMuxEnabled(config) {
 		logger.Info("main - multiplexer mode enabled")
-		InitMultiplexer(mapLoggers, mapCollectors, config, logger)
+		pkglinker.InitMultiplexer(mapLoggers, mapCollectors, config, logger)
+	}
+
+	// or pipeline ?
+	if len(config.Pipelines) > 0 {
+		logger.Info("main - pipelines mode enabled")
+		err := pkglinker.InitPipelines(mapLoggers, mapCollectors, config, logger)
+		if err != nil {
+			logger.Error("main - %s", err.Error())
+			os.Exit(1)
+		}
 	}
 
 	// Handle Ctrl-C with SIG TERM and SIGHUP
@@ -122,15 +141,15 @@ func main() {
 				logger.Info("main - SIGHUP received")
 
 				// read config
-				err := pkgconfig.ReloadConfig(configPath, config)
+				err := pkgconfig.ReloadConfig(configPath, config, dmRef)
 				if err != nil {
 					panic(fmt.Sprintf("main - reload config error:  %v", err))
 				}
 
 				// reload logger and multiplexer
 				InitLogger(logger, config)
-				if IsMuxEnabled(config) {
-					ReloadMultiplexer(mapLoggers, mapCollectors, config, logger)
+				if pkglinker.IsMuxEnabled(config) {
+					pkglinker.ReloadMultiplexer(mapLoggers, mapCollectors, config, logger)
 				}
 
 			case <-sigTerm:
diff --git a/dnsutils/constant.go b/dnsutils/constant.go
index 5e91c3a4..13d8b43b 100644
--- a/dnsutils/constant.go
+++ b/dnsutils/constant.go
@@ -16,4 +16,12 @@ const (
 	DNSTapClientQuery    = "CLIENT_QUERY"
 
 	DNSTapIdentityTest = "test_id"
+
+	MatchingModeInclude   = "include"
+	MatchingOpGreaterThan = "greater-than"
+	MatchingOpLowerThan   = "lower-than"
+	MatchingOpSource      = "match-source"
+	MatchingOpSourceKind  = "source-kind"
+	MatchingKindString    = "string_list"
+	MatchingKindRegexp    = "regexp_list"
 )
diff --git a/dnsutils/message.go b/dnsutils/message.go
index d4279c75..8a13b850 100644
--- a/dnsutils/message.go
+++ b/dnsutils/message.go
@@ -9,6 +9,7 @@ import (
 	"fmt"
 	"log"
 	"net"
+	"reflect"
 	"regexp"
 	"strconv"
 	"strings"
@@ -207,6 +208,10 @@ type TransformML struct {
 	UncommonQtypes        int     `json:"uncommon-qtypes" msgpack:"uncommon-qtypes"`
 }
 
+type TransformATags struct {
+	Tags []string `json:"tags" msgpack:"tags"`
+}
+
 type DNSMessage struct {
 	NetworkInfo     DNSNetInfo             `json:"network" msgpack:"network"`
 	DNS             DNS                    `json:"dns" msgpack:"dns"`
@@ -220,6 +225,7 @@ type DNSMessage struct {
 	Reducer         *TransformReducer      `json:"reducer,omitempty" msgpack:"reducer"`
 	MachineLearning *TransformML           `json:"ml,omitempty" msgpack:"ml"`
 	Filtering       *TransformFiltering    `json:"filtering,omitempty" msgpack:"filtering"`
+	ATags           *TransformATags        `json:"atags,omitempty" msgpack:"atags"`
 }
 
 func (dm *DNSMessage) Init() {
@@ -262,6 +268,18 @@ func (dm *DNSMessage) Init() {
 	}
 }
 
+func (dm *DNSMessage) InitTransforms() {
+	dm.ATags = &TransformATags{}
+	dm.Filtering = &TransformFiltering{}
+	dm.MachineLearning = &TransformML{}
+	dm.Reducer = &TransformReducer{}
+	dm.Extracted = &TransformExtracted{}
+	dm.PublicSuffix = &TransformPublicSuffix{}
+	dm.Suspicious = &TransformSuspicious{}
+	dm.PowerDNS = &PowerDNS{}
+	dm.Geo = &TransformDNSGeo{}
+}
+
 func (dm *DNSMessage) handleGeoIPDirectives(directives []string, s *strings.Builder) {
 	if dm.Geo == nil {
 		s.WriteString("-")
@@ -839,6 +857,475 @@ func (dm *DNSMessage) Flatten() (ret map[string]interface{}, err error) {
 	return
 }
 
+func (dm *DNSMessage) Matching(matching map[string]interface{}) (error, bool) {
+	if len(matching) == 0 {
+		return nil, false
+	}
+
+	dmValue := reflect.ValueOf(dm)
+
+	if dmValue.Kind() == reflect.Ptr {
+		dmValue = dmValue.Elem()
+	}
+
+	var isMatch = true
+
+	for nestedKeys, value := range matching {
+		realValue, found := getFieldByJSONTag(dmValue, nestedKeys)
+		if !found {
+			return nil, false
+		}
+
+		expectedValue := reflect.ValueOf(value)
+		// fmt.Println(nestedKeys, realValue, realValue.Kind(), expectedValue.Kind())
+
+		switch expectedValue.Kind() {
+		// integer
+		case reflect.Int:
+			if match, _ := matchUserInteger(realValue, expectedValue); !match {
+				return nil, false
+			}
+
+		// string
+		case reflect.String:
+			if match, _ := matchUserPattern(realValue, expectedValue); !match {
+				return nil, false
+			}
+
+		// bool
+		case reflect.Bool:
+			if match, _ := matchUserBoolean(realValue, expectedValue); !match {
+				return nil, false
+			}
+
+		// map
+		case reflect.Map:
+			if match, _ := matchUserMap(realValue, expectedValue); !match {
+				return nil, false
+			}
+
+		// list/slice
+		case reflect.Slice:
+			if match, _ := matchUserSlice(realValue, expectedValue); !match {
+				return nil, false
+			}
+
+		// other user types
+		default:
+			return fmt.Errorf("unsupported type value: %s", expectedValue.Kind()), false
+		}
+
+	}
+
+	return nil, isMatch
+}
+
+// map can be provided by user in the config
+// dns.qname:
+// match-source: "file://./testsdata/filtering_keep_domains_regex.txt"
+// source-kind: "regexp_list"
+func matchUserMap(realValue, expectedValue reflect.Value) (bool, error) {
+	for _, opKey := range expectedValue.MapKeys() {
+		opValue := expectedValue.MapIndex(opKey)
+		opName := opKey.Interface().(string)
+
+		switch opName {
+		// Integer great than ?
+		case MatchingOpGreaterThan:
+			if _, ok := opValue.Interface().(int); !ok {
+				return false, fmt.Errorf("integer is expected for greater-than operator")
+			}
+
+			// If realValue is a slice
+			if realValue.Kind() == reflect.Slice {
+				for i := 0; i < realValue.Len(); i++ {
+					elemValue := realValue.Index(i)
+
+					// Check if the element is a int
+					if _, ok := elemValue.Interface().(int); !ok {
+						continue
+					}
+
+					// Check for match
+					if elemValue.Interface().(int) > opValue.Interface().(int) {
+						return true, nil
+					}
+				}
+				return false, nil
+			}
+
+			if realValue.Kind() != reflect.Int {
+				return false, nil
+			}
+			if realValue.Interface().(int) > opValue.Interface().(int) {
+				return true, nil
+			}
+			return false, nil
+
+		// Integer lower than ?
+		case MatchingOpLowerThan:
+			if _, ok := opValue.Interface().(int); !ok {
+				return false, fmt.Errorf("integer is expected for lower-than operator")
+			}
+
+			// If realValue is a slice
+			if realValue.Kind() == reflect.Slice {
+				for i := 0; i < realValue.Len(); i++ {
+					elemValue := realValue.Index(i)
+
+					// Check if the element is a int
+					if _, ok := elemValue.Interface().(int); !ok {
+						continue
+					}
+
+					// Check for match
+					if elemValue.Interface().(int) < opValue.Interface().(int) {
+						return true, nil
+					}
+				}
+				return false, nil
+			}
+
+			if realValue.Kind() != reflect.Int {
+				return false, nil
+			}
+			if realValue.Interface().(int) < opValue.Interface().(int) {
+				return true, nil
+			}
+			return false, nil
+
+		// Ignore these operators
+		case MatchingOpSource, MatchingOpSourceKind:
+			continue
+
+		// List of pattern
+		case MatchingKindRegexp:
+			patternList := opValue.Interface().([]*regexp.Regexp)
+
+			// If realValue is a slice
+			if realValue.Kind() == reflect.Slice {
+				for i := 0; i < realValue.Len(); i++ {
+					elemValue := realValue.Index(i)
+
+					// Check if the element is a string
+					if _, ok := elemValue.Interface().(string); !ok {
+						continue
+					}
+
+					// Check for a match with the regex pattern
+					for _, pattern := range patternList {
+						if pattern.MatchString(elemValue.Interface().(string)) {
+							return true, nil
+						}
+					}
+				}
+				// No match found in the slice
+				return false, nil
+			}
+
+			if realValue.Kind() != reflect.String {
+				return false, nil
+			}
+			for _, pattern := range patternList {
+				if pattern.MatchString(realValue.Interface().(string)) {
+					return true, nil
+				}
+			}
+			// No match found in pattern list
+			return false, nil
+
+		// List of string
+		case MatchingKindString:
+			stringList := opValue.Interface().([]string)
+
+			// If realValue is a slice
+			if realValue.Kind() == reflect.Slice {
+				for i := 0; i < realValue.Len(); i++ {
+					elemValue := realValue.Index(i)
+
+					// Check if the element is a string
+					if _, ok := elemValue.Interface().(string); !ok {
+						continue
+					}
+
+					// Check for a match with the text
+					for _, textItem := range stringList {
+						if textItem == realValue.Interface().(string) {
+							return true, nil
+						}
+					}
+				}
+				// No match found in the slice
+				return false, nil
+			}
+
+			if realValue.Kind() != reflect.String {
+				return false, nil
+			}
+			for _, textItem := range stringList {
+				if textItem == realValue.Interface().(string) {
+					return true, nil
+				}
+			}
+
+			// No match found in string list
+			return false, nil
+
+		default:
+			return false, fmt.Errorf("invalid operator '%s', ignore it", opKey.Interface().(string))
+		}
+	}
+	return true, nil
+}
+
+// list can be provided by user in the config
+// dns.qname:
+//   - ".*\\.github\\.com$"
+//   - "^www\\.google\\.com$"
+func matchUserSlice(realValue, expectedValue reflect.Value) (bool, error) {
+	match := false
+	for i := 0; i < expectedValue.Len() && !match; i++ {
+		reflectedSub := reflect.ValueOf(expectedValue.Index(i).Interface())
+
+		switch reflectedSub.Kind() {
+		case reflect.Int:
+			if realValue.Kind() == reflect.Slice {
+				for i := 0; i < realValue.Len(); i++ {
+					elemValue := realValue.Index(i)
+					if _, ok := elemValue.Interface().(int); !ok {
+						continue
+					}
+					if reflectedSub.Interface().(int) == elemValue.Interface().(int) {
+						return true, nil
+					}
+				}
+			}
+
+			if realValue.Kind() != reflect.Int || realValue.Interface().(int) != reflectedSub.Interface().(int) {
+				continue
+			}
+			match = true
+		case reflect.String:
+			pattern := regexp.MustCompile(reflectedSub.Interface().(string))
+			if realValue.Kind() == reflect.Slice {
+				for i := 0; i < realValue.Len() && !match; i++ {
+					elemValue := realValue.Index(i)
+					if _, ok := elemValue.Interface().(string); !ok {
+						continue
+					}
+					// Check for a match with the regex pattern
+					if pattern.MatchString(elemValue.Interface().(string)) {
+						match = true
+					}
+				}
+			}
+
+			if realValue.Kind() != reflect.String {
+				continue
+			}
+
+			if pattern.MatchString(realValue.Interface().(string)) {
+				match = true
+			}
+		}
+	}
+	return match, nil
+}
+
+// boolean can be provided by user in the config
+// dns.flags.qr: true
+func matchUserBoolean(realValue, expectedValue reflect.Value) (bool, error) {
+	// If realValue is a slice
+	if realValue.Kind() == reflect.Slice {
+		for i := 0; i < realValue.Len(); i++ {
+			elemValue := realValue.Index(i)
+
+			// Check if the element is a int
+			if _, ok := elemValue.Interface().(bool); !ok {
+				continue
+			}
+
+			// Check for match
+			if expectedValue.Interface().(bool) == elemValue.Interface().(bool) {
+				return true, nil
+			}
+		}
+	}
+
+	if realValue.Kind() != reflect.Bool {
+		return false, nil
+	}
+
+	if expectedValue.Interface().(bool) != realValue.Interface().(bool) {
+		return false, nil
+	}
+	return true, nil
+}
+
+// integer can be provided by user in the config
+// dns.opcode: 0
+func matchUserInteger(realValue, expectedValue reflect.Value) (bool, error) {
+	// If realValue is a slice
+	if realValue.Kind() == reflect.Slice {
+		for i := 0; i < realValue.Len(); i++ {
+			elemValue := realValue.Index(i)
+
+			// Check if the element is a int
+			if _, ok := elemValue.Interface().(int); !ok {
+				continue
+			}
+
+			// Check for match
+			if expectedValue.Interface().(int) == elemValue.Interface().(int) {
+				return true, nil
+			}
+		}
+	}
+
+	if realValue.Kind() != reflect.Int {
+		return false, nil
+	}
+	if expectedValue.Interface().(int) != realValue.Interface().(int) {
+		return false, nil
+	}
+
+	return true, nil
+}
+
+// regexp can be provided by user in the config
+// dns.qname: "^.*\\.github\\.com$"
+func matchUserPattern(realValue, expectedValue reflect.Value) (bool, error) {
+	pattern := regexp.MustCompile(expectedValue.Interface().(string))
+
+	// If realValue is a slice
+	if realValue.Kind() == reflect.Slice {
+		for i := 0; i < realValue.Len(); i++ {
+			elemValue := realValue.Index(i)
+
+			// Check if the element is a string
+			if _, ok := elemValue.Interface().(string); !ok {
+				continue
+			}
+
+			// Check for a match with the regex pattern
+			if pattern.MatchString(elemValue.Interface().(string)) {
+				return true, nil
+			}
+		}
+		// No match found in the slice
+		return false, nil
+	}
+
+	// If realValue is not a string
+	if realValue.Kind() != reflect.String {
+		return false, nil
+	}
+
+	// Check for a match with the regex pattern
+	if !pattern.MatchString(realValue.String()) {
+		return false, nil
+	}
+
+	// Match found for a single value
+	return true, nil
+}
+
+func getFieldByJSONTag(value reflect.Value, nestedKeys string) (reflect.Value, bool) {
+	listKeys := strings.SplitN(nestedKeys, ".", 2)
+
+	for j, jsonKey := range listKeys {
+		// Iterate over the fields of the structure
+		for i := 0; i < value.NumField(); i++ {
+			field := value.Type().Field(i)
+
+			// Get JSON tag
+			tag := field.Tag.Get("json")
+			tagClean := strings.TrimSuffix(tag, ",omitempty")
+
+			// Check if the JSON tag matches
+			if tagClean == jsonKey {
+				// ptr
+				switch field.Type.Kind() {
+				// integer
+				case reflect.Ptr:
+					if fieldValue, found := getFieldByJSONTag(value.Field(i).Elem(), listKeys[j+1]); found {
+						return fieldValue, true
+					}
+
+				// struct
+				case reflect.Struct:
+					if fieldValue, found := getFieldByJSONTag(value.Field(i), listKeys[j+1]); found {
+						return fieldValue, true
+					}
+
+				// slice if a list
+				case reflect.Slice:
+					if fieldValue, leftKey, found := getSliceElement(value.Field(i), listKeys[j+1]); found {
+						switch field.Type.Kind() {
+						case reflect.Struct:
+							if fieldValue, found := getFieldByJSONTag(fieldValue, leftKey); found {
+								return fieldValue, true
+							}
+						case reflect.Slice:
+							var result []interface{}
+							for i := 0; i < fieldValue.Len(); i++ {
+
+								if fieldValue.Index(i).Kind() == reflect.String || fieldValue.Index(i).Kind() == reflect.Int {
+									result = append(result, fieldValue.Index(i).Interface())
+								} else {
+									if sliceValue, found := getFieldByJSONTag(fieldValue.Index(i), leftKey); found {
+										result = append(result, sliceValue.Interface())
+									}
+								}
+							}
+							// If the list is not empty, return the list
+							if len(result) > 0 {
+								return reflect.ValueOf(result), true
+							}
+						default:
+							return value.Field(i), true
+						}
+					}
+
+				// int, string
+				default:
+					return value.Field(i), true
+				}
+			}
+		}
+	}
+
+	return reflect.Value{}, false
+}
+
+func getSliceElement(sliceValue reflect.Value, nestedKeys string) (reflect.Value, string, bool) {
+	listKeys := strings.SplitN(nestedKeys, ".", 2)
+	leftKeys := ""
+	if len(listKeys) > 1 {
+		leftKeys = listKeys[1]
+	}
+	sliceIndex := listKeys[0]
+
+	if sliceIndex == "*" {
+		return sliceValue, leftKeys, true
+	}
+
+	// Convert the slice index from string to int
+	index, err := strconv.Atoi(sliceIndex)
+	if err != nil {
+		// Handle the error (e.g., invalid index format)
+		return reflect.Value{}, leftKeys, false
+	}
+
+	for i := 0; i < sliceValue.Len(); i++ {
+		if index == i {
+			return sliceValue.Index(i), leftKeys, true
+		}
+	}
+	// If no match is found, return an empty reflect.Value
+	return reflect.Value{}, leftKeys, false
+}
+
 func GetFakeDNSMessage() DNSMessage {
 	dm := DNSMessage{}
 	dm.Init()
@@ -868,3 +1355,11 @@ func GetFakeDNSMessageWithPayload() DNSMessage {
 	dm.DNS.Length = len(dnsquestion)
 	return dm
 }
+
+func GetFlatDNSMessage() (ret map[string]interface{}, err error) {
+	dm := DNSMessage{}
+	dm.Init()
+	dm.InitTransforms()
+	ret, err = dm.Flatten()
+	return
+}
diff --git a/dnsutils/worker.go b/dnsutils/worker.go
deleted file mode 100644
index 0d27c8c7..00000000
--- a/dnsutils/worker.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package dnsutils
-
-import "github.com/dmachard/go-dnscollector/pkgconfig"
-
-type Worker interface {
-	SetLoggers(loggers []Worker)
-	GetName() string
-	Stop()
-	Run()
-	Channel() chan DNSMessage
-	ReadConfig()
-	ReloadConfig(config *pkgconfig.Config)
-}
diff --git a/docs/development.md b/docs/development.md
index e36aa8d5..ce867c12 100644
--- a/docs/development.md
+++ b/docs/development.md
@@ -12,13 +12,28 @@ How to userguides:
 
 ## Build and run from source
 
-Building from source. Use the latest golang available on your target system.
+Building from source
+
+- The very fast way, go to the top of the project and run go command
+
+```bash
+go run .
+```
+
+- Uses the `MakeFile` (prefered way)
+
+```bash
+make
+```
+
+Execute the binary
 
 ```bash
-make build
 make run
 ```
 
+- From the `DockerFile`
+
 ## Run linters
 
 Install linter
@@ -178,7 +193,7 @@ func NewMyLogger(config *pkgconfig.Config, logger *logger.Logger, name string) *
 
 func (c *MyLogger) GetName() string { return c.name }
 
-func (c *MyLogger) SetLoggers(loggers []dnsutils.Worker) {}
+func (c *MyLogger) SetLoggers(loggers []pkgutils.Worker) {}
 
 func (o *MyLogger) ReadConfig() {}
 
@@ -201,7 +216,7 @@ func (o *MyLogger) Stop() {
  close(o.done)
 }
 
-func (o *MyLogger) Channel() chan dnsutils.DnsMessage {
+func (o *MyLogger) GetInputChannel() chan dnsutils.DnsMessage {
  return o.channel
 }
 
@@ -255,79 +270,145 @@ Create the following file `collectors/mycollector.go` and `collectors/mycollecto
 package collectors
 
 import (
- "github.com/dmachard/go-dnscollector/dnsutils"
- "github.com/dmachard/go-logger"
+    "time"
+
+    "github.com/dmachard/go-dnscollector/dnsutils"
+    "github.com/dmachard/go-dnscollector/pkgconfig"
+    "github.com/dmachard/go-logger"
 )
 
-type MyCollector struct {
- done    chan bool
- exit    chan bool
- loggers []dnsutils.Worker
- config  *pkgconfig.Config
- logger  *logger.Logger
- name    string
+type MyNewCollector struct {
+    doneRun      chan bool
+    doneMonitor  chan bool
+    stopRun      chan bool
+    stopMonitor  chan bool
+    loggers      []pkgutils.Worker
+    config       *pkgconfig.Config
+    configChan   chan *pkgconfig.Config
+    logger       *logger.Logger
+    name         string
+    droppedCount int
+    dropped      chan int
 }
 
-// workaround for macos, not yet supported
-func NewMyCollector(loggers []dnsutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *MyCollector {
- logger.Info("[%s] mycollector - enabled", name)
- s := &MyCollector{
-  done:    make(chan bool),
-  exit:    make(chan bool),
-  config:  config,
-  loggers: loggers,
-  logger:  logger,
-  name:    name,
- }
- s.ReadConfig()
- return s
+func NewNewCollector(loggers []pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger, name string) *Dnstap {
+    logger.Info("[%s] collector=mynewcollector - enabled", name)
+    s := &MyNewCollector{
+        doneRun:     make(chan bool),
+        doneMonitor: make(chan bool),
+        stopRun:     make(chan bool),
+        stopMonitor: make(chan bool),
+        dropped:     make(chan int),
+        config:      config,
+        configChan:  make(chan *pkgconfig.Config),
+        loggers:     loggers,
+        logger:      logger,
+        name:        name,
+    }
+    s.ReadConfig()
+    return s
 }
 
-func (c *MyCollector) GetName() string { return c.name }
+func (c *MyNewCollector) GetName() string { return c.name }
 
-func (c *MyCollector) SetLoggers(loggers []dnsutils.Worker) {
- c.loggers = loggers
+func (c *MyNewCollector) AddDefaultRoute(wrk pkgutils.Worker) {
+    c.loggers = append(c.loggers, wrk)
 }
 
-func (c *MyCollector) LogInfo(msg string, v ...interface{}) {
- c.logger.Info("["+c.name+"] mycollector - "+msg, v...)
+func (c *MyNewCollector) SetLoggers(loggers []pkgutils.Worker) {
+    c.loggers = loggers
 }
 
-func (c *MyCollector) LogError(msg string, v ...interface{}) {
- c.logger.Error("["+c.name+"] mycollector - "+msg, v...)
+func (c *MyNewCollector) Loggers() ([]chan dnsutils.DNSMessage, []string) {
+    channels := []chan dnsutils.DNSMessage{}
+    names := []string{}
+    for _, p := range c.loggers {
+        channels = append(channels,p.GetInputChannel())
+        names = append(names, p.GetName())
+    }
+    return channels, names
 }
 
-func (c *MyCollector) Loggers() []chan dnsutils.DnsMessage {
- channels := []chan dnsutils.DnsMessage{}
- for _, p := range c.loggers {
-  channels = append(channels, p.Channel())
- }
- return channels
+func (c *MyNewCollector) ReadConfig() {}
+
+func (c *MyNewCollector) ReloadConfig(config *pkgconfig.Config) {
+    c.LogInfo("reload configuration...")
+    c.configChan <- config
 }
 
-func (c *MyCollector) ReadConfig() {
+func (c *MyNewCollector) LogInfo(msg string, v ...interface{}) {
+    c.logger.Info("["+c.name+"] collector=mynewcollector - "+msg, v...)
 }
 
-func (c *MyCollector) Channel() chan dnsutils.DnsMessage {
- return nil
+func (c *MyNewCollector) LogError(msg string, v ...interface{}) {
+    c.logger.Error("["+c.name+" collector=mynewcollector - "+msg, v...)
 }
 
-func (c *MyCollector) Stop() {
- c.LogInfo("stopping...")
+func (c *MyNewCollector) GetInputChannel() chan dnsutils.DNSMessage {
+    return nil
+}
 
- // exit to close properly
- c.exit <- true
+func (c *MyNewCollector) Stop() {
+    // stop monitor goroutine
+    c.LogInfo("stopping monitor...")
+    c.stopMonitor <- true
+    <-c.doneMonitor
 
- // read done channel and block until run is terminated
- <-c.done
- close(c.done)
+    // read done channel and block until run is terminated
+    c.LogInfo("stopping run...")
+    c.stopRun <- true
+    <-c.doneRun
 }
 
-func (c *MyCollector) Run() {
- c.LogInfo("run terminated")
- c.done <- true
+func (c *MyNewCollector) MonitorCollector() {
+    watchInterval := 10 * time.Second
+    bufferFull := time.NewTimer(watchInterval)
+MONITOR_LOOP:
+    for {
+        select {
+            case <-c.dropped:
+                c.droppedCount++
+            case <-c.stopMonitor:
+                close(c.dropped)
+                bufferFull.Stop()
+                c.doneMonitor <- true
+                break MONITOR_LOOP
+            case <-bufferFull.C:
+                if c.droppedCount > 0 {
+                    c.LogError("recv buffer is full, %d packet(s) dropped", c.droppedCount)
+                    c.droppedCount = 0
+                }
+                bufferFull.Reset(watchInterval)
+        }
+    }
+    c.LogInfo("monitor terminated")
 }
 
+func (c *DNSMessage) Run() {
+    c.LogInfo("starting collector...")
+
+    // start goroutine to count dropped messsages
+    go c.MonitorCollector()
+
+RUN_LOOP:
+    for {
+        select {
+        case <-c.stopRun:
+            c.doneRun <- true
+            break RUN_LOOP
+
+        case cfg := <-c.configChan:
+
+            // save the new config
+            c.config = cfg
+            c.ReadConfig()
+        }
+
+    }
+    c.LogInfo("run terminated")
+}
+
+
 ```
 
 Update the main file `dnscollector.go`
diff --git a/loggers/dnstapclient.go b/loggers/dnstapclient.go
index 5d493137..305b55ec 100644
--- a/loggers/dnstapclient.go
+++ b/loggers/dnstapclient.go
@@ -10,6 +10,7 @@ import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/netlib"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/transformers"
 	"github.com/dmachard/go-framestream"
 	"github.com/dmachard/go-logger"
@@ -32,11 +33,12 @@ type DnstapSender struct {
 	transportReady     chan bool
 	transportReconnect chan bool
 	name               string
+	RoutingHandler     pkgutils.RoutingHandler
 }
 
 func NewDnstapSender(config *pkgconfig.Config, logger *logger.Logger, name string) *DnstapSender {
 	logger.Info("[%s] logger=dnstap - enabled", name)
-	s := &DnstapSender{
+	ds := &DnstapSender{
 		stopProcess:        make(chan bool),
 		doneProcess:        make(chan bool),
 		stopRun:            make(chan bool),
@@ -49,120 +51,131 @@ func NewDnstapSender(config *pkgconfig.Config, logger *logger.Logger, name strin
 		config:             config,
 		configChan:         make(chan *pkgconfig.Config),
 		name:               name,
+		RoutingHandler:     pkgutils.NewRoutingHandler(config, logger, name),
 	}
 
-	s.ReadConfig()
+	ds.ReadConfig()
+	return ds
+}
+
+func (ds *DnstapSender) GetName() string { return ds.name }
 
-	return s
+func (ds *DnstapSender) AddDroppedRoute(wrk pkgutils.Worker) {
+	ds.RoutingHandler.AddDroppedRoute(wrk)
 }
 
-func (c *DnstapSender) GetName() string { return c.name }
+func (ds *DnstapSender) AddDefaultRoute(wrk pkgutils.Worker) {
+	ds.RoutingHandler.AddDefaultRoute(wrk)
+}
 
-func (c *DnstapSender) SetLoggers(loggers []dnsutils.Worker) {}
+func (ds *DnstapSender) SetLoggers(loggers []pkgutils.Worker) {}
 
-func (c *DnstapSender) ReadConfig() {
-	c.transport = c.config.Loggers.DNSTap.Transport
+func (ds *DnstapSender) ReadConfig() {
+	ds.transport = ds.config.Loggers.DNSTap.Transport
 
 	// begin backward compatibility
-	if c.config.Loggers.DNSTap.TLSSupport {
-		c.transport = netlib.SocketTLS
+	if ds.config.Loggers.DNSTap.TLSSupport {
+		ds.transport = netlib.SocketTLS
 	}
-	if len(c.config.Loggers.DNSTap.SockPath) > 0 {
-		c.transport = netlib.SocketUnix
+	if len(ds.config.Loggers.DNSTap.SockPath) > 0 {
+		ds.transport = netlib.SocketUnix
 	}
 	// end
 
 	// get hostname or global one
-	if c.config.Loggers.DNSTap.ServerID == "" {
-		c.config.Loggers.DNSTap.ServerID = c.config.GetServerIdentity()
+	if ds.config.Loggers.DNSTap.ServerID == "" {
+		ds.config.Loggers.DNSTap.ServerID = ds.config.GetServerIdentity()
 	}
 
-	if !pkgconfig.IsValidTLS(c.config.Loggers.DNSTap.TLSMinVersion) {
-		c.logger.Fatal("logger=dnstap - invalid tls min version")
+	if !pkgconfig.IsValidTLS(ds.config.Loggers.DNSTap.TLSMinVersion) {
+		ds.logger.Fatal("logger=dnstap - invalid tls min version")
 	}
 }
 
-func (c *DnstapSender) ReloadConfig(config *pkgconfig.Config) {
-	c.LogInfo("reload configuration!")
-	c.configChan <- config
+func (ds *DnstapSender) ReloadConfig(config *pkgconfig.Config) {
+	ds.LogInfo("reload configuration!")
+	ds.configChan <- config
 }
 
-func (c *DnstapSender) LogInfo(msg string, v ...interface{}) {
-	c.logger.Info("["+c.name+"] logger=dnstap - "+msg, v...)
+func (ds *DnstapSender) LogInfo(msg string, v ...interface{}) {
+	ds.logger.Info("["+ds.name+"] logger=dnstap - "+msg, v...)
 }
 
-func (c *DnstapSender) LogError(msg string, v ...interface{}) {
-	c.logger.Error("["+c.name+"] logger=dnstap - "+msg, v...)
+func (ds *DnstapSender) LogError(msg string, v ...interface{}) {
+	ds.logger.Error("["+ds.name+"] logger=dnstap - "+msg, v...)
 }
 
-func (c *DnstapSender) Channel() chan dnsutils.DNSMessage {
-	return c.inputChan
+func (ds *DnstapSender) GetInputChannel() chan dnsutils.DNSMessage {
+	return ds.inputChan
 }
 
-func (c *DnstapSender) Stop() {
-	c.LogInfo("stopping to run...")
-	c.stopRun <- true
-	<-c.doneRun
+func (ds *DnstapSender) Stop() {
+	ds.LogInfo("stopping routing handler...")
+	ds.RoutingHandler.Stop()
 
-	c.LogInfo("stopping to process...")
-	c.stopProcess <- true
-	<-c.doneProcess
+	ds.LogInfo("stopping to run...")
+	ds.stopRun <- true
+	<-ds.doneRun
+
+	ds.LogInfo("stopping to process...")
+	ds.stopProcess <- true
+	<-ds.doneProcess
 }
 
-func (c *DnstapSender) Disconnect() {
-	if c.transportConn != nil {
+func (ds *DnstapSender) Disconnect() {
+	if ds.transportConn != nil {
 		// reset framestream and ignore errors
-		c.LogInfo("closing framestream")
-		c.fs.ResetSender()
+		ds.LogInfo("closing framestream")
+		ds.fs.ResetSender()
 
 		// closing tcp
-		c.LogInfo("closing tcp connection")
-		c.transportConn.Close()
-		c.LogInfo("closed")
+		ds.LogInfo("closing tcp connection")
+		ds.transportConn.Close()
+		ds.LogInfo("closed")
 	}
 }
 
-func (c *DnstapSender) ConnectToRemote() {
+func (ds *DnstapSender) ConnectToRemote() {
 	for {
-		if c.transportConn != nil {
-			c.transportConn.Close()
-			c.transportConn = nil
+		if ds.transportConn != nil {
+			ds.transportConn.Close()
+			ds.transportConn = nil
 		}
 
 		address := net.JoinHostPort(
-			c.config.Loggers.DNSTap.RemoteAddress,
-			strconv.Itoa(c.config.Loggers.DNSTap.RemotePort),
+			ds.config.Loggers.DNSTap.RemoteAddress,
+			strconv.Itoa(ds.config.Loggers.DNSTap.RemotePort),
 		)
-		connTimeout := time.Duration(c.config.Loggers.DNSTap.ConnectTimeout) * time.Second
+		connTimeout := time.Duration(ds.config.Loggers.DNSTap.ConnectTimeout) * time.Second
 
 		// make the connection
 		var conn net.Conn
 		var err error
 
-		switch c.transport {
+		switch ds.transport {
 		case netlib.SocketUnix:
-			address = c.config.Loggers.DNSTap.RemoteAddress
-			if len(c.config.Loggers.DNSTap.SockPath) > 0 {
-				address = c.config.Loggers.DNSTap.SockPath
+			address = ds.config.Loggers.DNSTap.RemoteAddress
+			if len(ds.config.Loggers.DNSTap.SockPath) > 0 {
+				address = ds.config.Loggers.DNSTap.SockPath
 			}
-			c.LogInfo("connecting to %s://%s", c.transport, address)
-			conn, err = net.DialTimeout(c.transport, address, connTimeout)
+			ds.LogInfo("connecting to %s://%s", ds.transport, address)
+			conn, err = net.DialTimeout(ds.transport, address, connTimeout)
 
 		case netlib.SocketTCP:
-			c.LogInfo("connecting to %s://%s", c.transport, address)
-			conn, err = net.DialTimeout(c.transport, address, connTimeout)
+			ds.LogInfo("connecting to %s://%s", ds.transport, address)
+			conn, err = net.DialTimeout(ds.transport, address, connTimeout)
 
 		case netlib.SocketTLS:
-			c.LogInfo("connecting to %s://%s", c.transport, address)
+			ds.LogInfo("connecting to %s://%s", ds.transport, address)
 
 			var tlsConfig *tls.Config
 
 			tlsOptions := pkgconfig.TLSOptions{
-				InsecureSkipVerify: c.config.Loggers.DNSTap.TLSInsecure,
-				MinVersion:         c.config.Loggers.DNSTap.TLSMinVersion,
-				CAFile:             c.config.Loggers.DNSTap.CAFile,
-				CertFile:           c.config.Loggers.DNSTap.CertFile,
-				KeyFile:            c.config.Loggers.DNSTap.KeyFile,
+				InsecureSkipVerify: ds.config.Loggers.DNSTap.TLSInsecure,
+				MinVersion:         ds.config.Loggers.DNSTap.TLSMinVersion,
+				CAFile:             ds.config.Loggers.DNSTap.CAFile,
+				CertFile:           ds.config.Loggers.DNSTap.CertFile,
+				KeyFile:            ds.config.Loggers.DNSTap.KeyFile,
 			}
 
 			tlsConfig, err = pkgconfig.TLSClientConfig(tlsOptions)
@@ -171,28 +184,28 @@ func (c *DnstapSender) ConnectToRemote() {
 				conn, err = tls.DialWithDialer(dialer, netlib.SocketTCP, address, tlsConfig)
 			}
 		default:
-			c.logger.Fatal("logger=dnstap - invalid transport:", c.transport)
+			ds.logger.Fatal("logger=dnstap - invalid transport:", ds.transport)
 		}
 
 		// something is wrong during connection ?
 		if err != nil {
-			c.LogError("%s", err)
-			c.LogInfo("retry to connect in %d seconds", c.config.Loggers.DNSTap.RetryInterval)
-			time.Sleep(time.Duration(c.config.Loggers.DNSTap.RetryInterval) * time.Second)
+			ds.LogError("%s", err)
+			ds.LogInfo("retry to connect in %d seconds", ds.config.Loggers.DNSTap.RetryInterval)
+			time.Sleep(time.Duration(ds.config.Loggers.DNSTap.RetryInterval) * time.Second)
 			continue
 		}
 
-		c.transportConn = conn
+		ds.transportConn = conn
 
 		// block until framestream is ready
-		c.transportReady <- true
+		ds.transportReady <- true
 
 		// block until an error occured, need to reconnect
-		c.transportReconnect <- true
+		ds.transportReconnect <- true
 	}
 }
 
-func (c *DnstapSender) FlushBuffer(buf *[]dnsutils.DNSMessage) {
+func (ds *DnstapSender) FlushBuffer(buf *[]dnsutils.DNSMessage) {
 
 	var data []byte
 	var err error
@@ -200,23 +213,23 @@ func (c *DnstapSender) FlushBuffer(buf *[]dnsutils.DNSMessage) {
 
 	for _, dm := range *buf {
 		// update identity ?
-		if c.config.Loggers.DNSTap.OverwriteIdentity {
-			dm.DNSTap.Identity = c.config.Loggers.DNSTap.ServerID
+		if ds.config.Loggers.DNSTap.OverwriteIdentity {
+			dm.DNSTap.Identity = ds.config.Loggers.DNSTap.ServerID
 		}
 
 		// encode dns message to dnstap protobuf binary
 		data, err = dm.ToDNSTap()
 		if err != nil {
-			c.LogError("failed to encode to DNStap protobuf: %s", err)
+			ds.LogError("failed to encode to DNStap protobuf: %s", err)
 			continue
 		}
 
 		// send the frame
 		frame.Write(data)
-		if err := c.fs.SendFrame(frame); err != nil {
-			c.LogError("send frame error %s", err)
-			c.fsReady = false
-			<-c.transportReconnect
+		if err := ds.fs.SendFrame(frame); err != nil {
+			ds.LogError("send frame error %s", err)
+			ds.fsReady = false
+			<-ds.transportReconnect
 			break
 		}
 	}
@@ -225,105 +238,123 @@ func (c *DnstapSender) FlushBuffer(buf *[]dnsutils.DNSMessage) {
 	*buf = nil
 }
 
-func (c *DnstapSender) Run() {
-	c.LogInfo("running in background...")
+func (ds *DnstapSender) Run() {
+	ds.LogInfo("running in background...")
+
+	// prepare next channels
+	defaultRoutes, defaultNames := ds.RoutingHandler.GetDefaultRoutes()
+	droppedRoutes, droppedNames := ds.RoutingHandler.GetDroppedRoutes()
 
 	// prepare transforms
 	listChannel := []chan dnsutils.DNSMessage{}
-	listChannel = append(listChannel, c.outputChan)
-	subprocessors := transformers.NewTransforms(&c.config.OutgoingTransformers, c.logger, c.name, listChannel, 0)
+	listChannel = append(listChannel, ds.outputChan)
+	subprocessors := transformers.NewTransforms(&ds.config.OutgoingTransformers, ds.logger, ds.name, listChannel, 0)
 
 	// goroutine to process transformed dns messages
-	go c.Process()
+	go ds.Process()
 
 	// init remote conn
-	go c.ConnectToRemote()
+	go ds.ConnectToRemote()
 
 	// loop to process incoming messages
 RUN_LOOP:
 	for {
 		select {
-		case <-c.stopRun:
+		case <-ds.stopRun:
 			// cleanup transformers
 			subprocessors.Reset()
 
-			c.doneRun <- true
+			ds.doneRun <- true
 			break RUN_LOOP
 
-		case cfg, opened := <-c.configChan:
+		case cfg, opened := <-ds.configChan:
 			if !opened {
 				return
 			}
-			c.config = cfg
-			c.ReadConfig()
+			ds.config = cfg
+			ds.ReadConfig()
 			subprocessors.ReloadConfig(&cfg.OutgoingTransformers)
 
-		case dm, opened := <-c.inputChan:
+		case dm, opened := <-ds.inputChan:
 			if !opened {
-				c.LogInfo("input channel closed!")
+				ds.LogInfo("input channel closed!")
 				return
 			}
 
 			// apply tranforms, init dns message with additionnals parts if necessary
 			subprocessors.InitDNSMessageFormat(&dm)
 			if subprocessors.ProcessMessage(&dm) == transformers.ReturnDrop {
+				ds.RoutingHandler.SendTo(droppedRoutes, droppedNames, dm)
 				continue
 			}
 
+			// send to next ?
+			ds.RoutingHandler.SendTo(defaultRoutes, defaultNames, dm)
+
 			// send to output channel
-			c.outputChan <- dm
+			ds.outputChan <- dm
 		}
 	}
-	c.LogInfo("run terminated")
+	ds.LogInfo("run terminated")
 }
 
-func (c *DnstapSender) Process() {
+func (ds *DnstapSender) Process() {
 	// init buffer
 	bufferDm := []dnsutils.DNSMessage{}
 
 	// init flust timer for buffer
-	flushInterval := time.Duration(c.config.Loggers.DNSTap.FlushInterval) * time.Second
+	flushInterval := time.Duration(ds.config.Loggers.DNSTap.FlushInterval) * time.Second
 	flushTimer := time.NewTimer(flushInterval)
 
-	c.LogInfo("ready to process")
+	// nextStanzaBufferInterval := 10 * time.Second
+	// nextStanzaBufferFull := time.NewTimer(nextStanzaBufferInterval)
+
+	ds.LogInfo("ready to process")
 PROCESS_LOOP:
 	for {
 		select {
-		case <-c.stopProcess:
+		case <-ds.stopProcess:
 			// closing remote connection if exist
-			c.Disconnect()
+			ds.Disconnect()
 
-			c.doneProcess <- true
+			ds.doneProcess <- true
 			break PROCESS_LOOP
 
-			// init framestream
-		case <-c.transportReady:
-			c.LogInfo("transport connected with success")
+		// case stanzaName := <-ds.dropped:
+		// 	if _, ok := ds.droppedCount[stanzaName]; !ok {
+		// 		ds.droppedCount[stanzaName] = 1
+		// 	} else {
+		// 		ds.droppedCount[stanzaName]++
+		// 	}
+
+		// init framestream
+		case <-ds.transportReady:
+			ds.LogInfo("transport connected with success")
 			// frame stream library
-			r := bufio.NewReader(c.transportConn)
-			w := bufio.NewWriter(c.transportConn)
-			c.fs = framestream.NewFstrm(r, w, c.transportConn, 5*time.Second, []byte("protobuf:dnstap.Dnstap"), true)
+			r := bufio.NewReader(ds.transportConn)
+			w := bufio.NewWriter(ds.transportConn)
+			ds.fs = framestream.NewFstrm(r, w, ds.transportConn, 5*time.Second, []byte("protobuf:dnstap.Dnstap"), true)
 
 			// init framestream protocol
-			if err := c.fs.InitSender(); err != nil {
-				c.LogError("sender protocol initialization error %s", err)
-				c.fsReady = false
-				c.transportConn.Close()
-				<-c.transportReconnect
+			if err := ds.fs.InitSender(); err != nil {
+				ds.LogError("sender protocol initialization error %s", err)
+				ds.fsReady = false
+				ds.transportConn.Close()
+				<-ds.transportReconnect
 			} else {
-				c.fsReady = true
-				c.LogInfo("framestream initialized with success")
+				ds.fsReady = true
+				ds.LogInfo("framestream initialized with success")
 			}
 		// incoming dns message to process
-		case dm, opened := <-c.outputChan:
+		case dm, opened := <-ds.outputChan:
 			if !opened {
-				c.LogInfo("output channel closed!")
+				ds.LogInfo("output channel closed!")
 				return
 			}
 
 			// drop dns message if the connection is not ready to avoid memory leak or
 			// to block the channel
-			if !c.fsReady {
+			if !ds.fsReady {
 				continue
 			}
 
@@ -331,20 +362,29 @@ PROCESS_LOOP:
 			bufferDm = append(bufferDm, dm)
 
 			// buffer is full ?
-			if len(bufferDm) >= c.config.Loggers.DNSTap.BufferSize {
-				c.FlushBuffer(&bufferDm)
+			if len(bufferDm) >= ds.config.Loggers.DNSTap.BufferSize {
+				ds.FlushBuffer(&bufferDm)
 			}
 
 		// flush the buffer
 		case <-flushTimer.C:
 			// force to flush the buffer
 			if len(bufferDm) > 0 {
-				c.FlushBuffer(&bufferDm)
+				ds.FlushBuffer(&bufferDm)
 			}
 
 			// restart timer
 			flushTimer.Reset(flushInterval)
+
+			// case <-nextStanzaBufferFull.C:
+			// 	for v, k := range ds.droppedCount {
+			// 		if k > 0 {
+			// 			ds.LogError("stanza[%s] buffer is full, %d packet(s) dropped", v, k)
+			// 			ds.droppedCount[v] = 0
+			// 		}
+			// 	}
+			// 	nextStanzaBufferFull.Reset(nextStanzaBufferInterval)
 		}
 	}
-	c.LogInfo("processing terminated")
+	ds.LogInfo("processing terminated")
 }
diff --git a/loggers/dnstapclient_test.go b/loggers/dnstapclient_test.go
index 195764eb..f67b1623 100644
--- a/loggers/dnstapclient_test.go
+++ b/loggers/dnstapclient_test.go
@@ -70,7 +70,7 @@ func Test_DnstapClient(t *testing.T) {
 
 			// send fake dns message to logger
 			dm := dnsutils.GetFakeDNSMessage()
-			g.Channel() <- dm
+			g.GetInputChannel() <- dm
 
 			// receive frame on server side ?, timeout 5s
 			fs, err := fsSvr.RecvFrame(true)
diff --git a/loggers/elasticsearch.go b/loggers/elasticsearch.go
index 735aacec..9da57d1b 100644
--- a/loggers/elasticsearch.go
+++ b/loggers/elasticsearch.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/transformers"
 	"github.com/dmachard/go-logger"
 
@@ -16,132 +17,153 @@ import (
 )
 
 type ElasticSearchClient struct {
-	stopProcess chan bool
-	doneProcess chan bool
-	stopRun     chan bool
-	doneRun     chan bool
-	inputChan   chan dnsutils.DNSMessage
-	outputChan  chan dnsutils.DNSMessage
-	config      *pkgconfig.Config
-	configChan  chan *pkgconfig.Config
-	logger      *logger.Logger
-	name        string
-	server      string
-	index       string
-	bulkURL     string
+	stopProcess    chan bool
+	doneProcess    chan bool
+	stopRun        chan bool
+	doneRun        chan bool
+	inputChan      chan dnsutils.DNSMessage
+	outputChan     chan dnsutils.DNSMessage
+	config         *pkgconfig.Config
+	configChan     chan *pkgconfig.Config
+	logger         *logger.Logger
+	name           string
+	server         string
+	index          string
+	bulkURL        string
+	RoutingHandler pkgutils.RoutingHandler
 }
 
 func NewElasticSearchClient(config *pkgconfig.Config, console *logger.Logger, name string) *ElasticSearchClient {
 	console.Info("[%s] logger=elasticsearch - enabled", name)
-	c := &ElasticSearchClient{
-		stopProcess: make(chan bool),
-		doneProcess: make(chan bool),
-		stopRun:     make(chan bool),
-		doneRun:     make(chan bool),
-		inputChan:   make(chan dnsutils.DNSMessage, config.Loggers.ElasticSearchClient.ChannelBufferSize),
-		outputChan:  make(chan dnsutils.DNSMessage, config.Loggers.ElasticSearchClient.ChannelBufferSize),
-		logger:      console,
-		config:      config,
-		configChan:  make(chan *pkgconfig.Config),
-		name:        name,
+	ec := &ElasticSearchClient{
+		stopProcess:    make(chan bool),
+		doneProcess:    make(chan bool),
+		stopRun:        make(chan bool),
+		doneRun:        make(chan bool),
+		inputChan:      make(chan dnsutils.DNSMessage, config.Loggers.ElasticSearchClient.ChannelBufferSize),
+		outputChan:     make(chan dnsutils.DNSMessage, config.Loggers.ElasticSearchClient.ChannelBufferSize),
+		logger:         console,
+		config:         config,
+		configChan:     make(chan *pkgconfig.Config),
+		name:           name,
+		RoutingHandler: pkgutils.NewRoutingHandler(config, console, name),
 	}
-	c.ReadConfig()
-	return c
+	ec.ReadConfig()
+	return ec
 }
 
-func (c *ElasticSearchClient) GetName() string { return c.name }
+func (ec *ElasticSearchClient) GetName() string { return ec.name }
 
-func (c *ElasticSearchClient) SetLoggers(loggers []dnsutils.Worker) {}
+func (ec *ElasticSearchClient) AddDroppedRoute(wrk pkgutils.Worker) {
+	ec.RoutingHandler.AddDroppedRoute(wrk)
+}
+
+func (ec *ElasticSearchClient) AddDefaultRoute(wrk pkgutils.Worker) {
+	ec.RoutingHandler.AddDefaultRoute(wrk)
+}
+
+func (ec *ElasticSearchClient) SetLoggers(loggers []pkgutils.Worker) {}
 
-func (c *ElasticSearchClient) ReadConfig() {
-	c.server = c.config.Loggers.ElasticSearchClient.Server
-	c.index = c.config.Loggers.ElasticSearchClient.Index
+func (ec *ElasticSearchClient) ReadConfig() {
+	ec.server = ec.config.Loggers.ElasticSearchClient.Server
+	ec.index = ec.config.Loggers.ElasticSearchClient.Index
 
-	u, err := url.Parse(c.server)
+	u, err := url.Parse(ec.server)
 	if err != nil {
-		c.LogError(err.Error())
+		ec.LogError(err.Error())
 	}
-	u.Path = path.Join(u.Path, c.index, "_bulk")
-	c.bulkURL = u.String()
+	u.Path = path.Join(u.Path, ec.index, "_bulk")
+	ec.bulkURL = u.String()
 }
 
-func (c *ElasticSearchClient) ReloadConfig(config *pkgconfig.Config) {
-	c.LogInfo("reload configuration!")
-	c.configChan <- config
+func (ec *ElasticSearchClient) ReloadConfig(config *pkgconfig.Config) {
+	ec.LogInfo("reload configuration!")
+	ec.configChan <- config
 }
 
-func (c *ElasticSearchClient) Channel() chan dnsutils.DNSMessage {
-	return c.inputChan
+func (ec *ElasticSearchClient) GetInputChannel() chan dnsutils.DNSMessage {
+	return ec.inputChan
 }
 
-func (c *ElasticSearchClient) LogInfo(msg string, v ...interface{}) {
-	c.logger.Info("["+c.name+"] logger=elasticsearch - "+msg, v...)
+func (ec *ElasticSearchClient) LogInfo(msg string, v ...interface{}) {
+	ec.logger.Info("["+ec.name+"] logger=elasticsearch - "+msg, v...)
 }
 
-func (c *ElasticSearchClient) LogError(msg string, v ...interface{}) {
-	c.logger.Error("["+c.name+"] logger=elasticsearch - "+msg, v...)
+func (ec *ElasticSearchClient) LogError(msg string, v ...interface{}) {
+	ec.logger.Error("["+ec.name+"] logger=elasticsearch - "+msg, v...)
 }
 
-func (c *ElasticSearchClient) Stop() {
-	c.LogInfo("stopping to run...")
-	c.stopRun <- true
-	<-c.doneRun
+func (ec *ElasticSearchClient) Stop() {
+	ec.LogInfo("stopping routing handler...")
+	ec.RoutingHandler.Stop()
 
-	c.LogInfo("stopping to process...")
-	c.stopProcess <- true
-	<-c.doneProcess
+	ec.LogInfo("stopping to run...")
+	ec.stopRun <- true
+	<-ec.doneRun
+
+	ec.LogInfo("stopping to process...")
+	ec.stopProcess <- true
+	<-ec.doneProcess
 }
 
-func (c *ElasticSearchClient) Run() {
-	c.LogInfo("running in background...")
+func (ec *ElasticSearchClient) Run() {
+	ec.LogInfo("running in background...")
+
+	// prepare next channels
+	defaultRoutes, defaultNames := ec.RoutingHandler.GetDefaultRoutes()
+	droppedRoutes, droppedNames := ec.RoutingHandler.GetDroppedRoutes()
 
 	// prepare transforms
 	listChannel := []chan dnsutils.DNSMessage{}
-	listChannel = append(listChannel, c.outputChan)
-	subprocessors := transformers.NewTransforms(&c.config.OutgoingTransformers, c.logger, c.name, listChannel, 0)
+	listChannel = append(listChannel, ec.outputChan)
+	subprocessors := transformers.NewTransforms(&ec.config.OutgoingTransformers, ec.logger, ec.name, listChannel, 0)
 
 	// goroutine to process transformed dns messages
-	go c.Process()
+	go ec.Process()
 
 	// loop to process incoming messages
 RUN_LOOP:
 	for {
 		select {
-		case <-c.stopRun:
+		case <-ec.stopRun:
 			// cleanup transformers
 			subprocessors.Reset()
 
-			c.doneRun <- true
+			ec.doneRun <- true
 			break RUN_LOOP
 
-		case cfg, opened := <-c.configChan:
+		case cfg, opened := <-ec.configChan:
 			if !opened {
 				return
 			}
-			c.config = cfg
-			c.ReadConfig()
+			ec.config = cfg
+			ec.ReadConfig()
 			subprocessors.ReloadConfig(&cfg.OutgoingTransformers)
 
-		case dm, opened := <-c.inputChan:
+		case dm, opened := <-ec.inputChan:
 			if !opened {
-				c.LogInfo("input channel closed!")
+				ec.LogInfo("input channel closed!")
 				return
 			}
 
 			// apply tranforms, init dns message with additionnals parts if necessary
 			subprocessors.InitDNSMessageFormat(&dm)
 			if subprocessors.ProcessMessage(&dm) == transformers.ReturnDrop {
+				ec.RoutingHandler.SendTo(droppedRoutes, droppedNames, dm)
 				continue
 			}
 
+			// send to next ?
+			ec.RoutingHandler.SendTo(defaultRoutes, defaultNames, dm)
+
 			// send to output channel
-			c.outputChan <- dm
+			ec.outputChan <- dm
 		}
 	}
-	c.LogInfo("run terminated")
+	ec.LogInfo("run terminated")
 }
 
-func (c *ElasticSearchClient) FlushBuffer(buf *[]dnsutils.DNSMessage) {
+func (ec *ElasticSearchClient) FlushBuffer(buf *[]dnsutils.DNSMessage) {
 	buffer := new(bytes.Buffer)
 
 	for _, dm := range *buf {
@@ -150,42 +172,42 @@ func (c *ElasticSearchClient) FlushBuffer(buf *[]dnsutils.DNSMessage) {
 		// encode
 		flat, err := dm.Flatten()
 		if err != nil {
-			c.LogError("flattening DNS message failed: %e", err)
+			ec.LogError("flattening DNS message failed: %e", err)
 		}
 		json.NewEncoder(buffer).Encode(flat)
 	}
 
-	req, _ := http.NewRequest("POST", c.bulkURL, buffer)
+	req, _ := http.NewRequest("POST", ec.bulkURL, buffer)
 	req.Header.Set("Content-Type", "application/json")
 	client := &http.Client{
 		Timeout: 5 * time.Second,
 	}
 	_, err := client.Do(req)
 	if err != nil {
-		c.LogError(err.Error())
+		ec.LogError(err.Error())
 	}
 
 	*buf = nil
 }
 
-func (c *ElasticSearchClient) Process() {
+func (ec *ElasticSearchClient) Process() {
 	bufferDm := []dnsutils.DNSMessage{}
-	c.LogInfo("ready to process")
+	ec.LogInfo("ready to process")
 
-	flushInterval := time.Duration(c.config.Loggers.ElasticSearchClient.FlushInterval) * time.Second
+	flushInterval := time.Duration(ec.config.Loggers.ElasticSearchClient.FlushInterval) * time.Second
 	flushTimer := time.NewTimer(flushInterval)
 
 PROCESS_LOOP:
 	for {
 		select {
-		case <-c.stopProcess:
-			c.doneProcess <- true
+		case <-ec.stopProcess:
+			ec.doneProcess <- true
 			break PROCESS_LOOP
 
 		// incoming dns message to process
-		case dm, opened := <-c.outputChan:
+		case dm, opened := <-ec.outputChan:
 			if !opened {
-				c.LogInfo("output channel closed!")
+				ec.LogInfo("output channel closed!")
 				return
 			}
 
@@ -193,18 +215,18 @@ PROCESS_LOOP:
 			bufferDm = append(bufferDm, dm)
 
 			// buffer is full ?
-			if len(bufferDm) >= c.config.Loggers.ElasticSearchClient.BulkSize {
-				c.FlushBuffer(&bufferDm)
+			if len(bufferDm) >= ec.config.Loggers.ElasticSearchClient.BulkSize {
+				ec.FlushBuffer(&bufferDm)
 			}
 			// flush the buffer
 		case <-flushTimer.C:
 			if len(bufferDm) > 0 {
-				c.FlushBuffer(&bufferDm)
+				ec.FlushBuffer(&bufferDm)
 			}
 
 			// restart timer
 			flushTimer.Reset(flushInterval)
 		}
 	}
-	c.LogInfo("processing terminated")
+	ec.LogInfo("processing terminated")
 }
diff --git a/loggers/elasticsearch_test.go b/loggers/elasticsearch_test.go
index 101eef88..a2f141c4 100644
--- a/loggers/elasticsearch_test.go
+++ b/loggers/elasticsearch_test.go
@@ -47,7 +47,7 @@ func Test_ElasticSearchClient(t *testing.T) {
 			dm := dnsutils.GetFakeDNSMessage()
 
 			for i := 0; i < tc.inputSize; i++ {
-				g.Channel() <- dm
+				g.GetInputChannel() <- dm
 			}
 
 			for i := 0; i < tc.inputSize/tc.bulkSize; i++ {
@@ -130,7 +130,7 @@ func Test_ElasticSearchClientFlushINterval(t *testing.T) {
 			dm := dnsutils.GetFakeDNSMessage()
 
 			for i := 0; i < tc.inputSize; i++ {
-				g.Channel() <- dm
+				g.GetInputChannel() <- dm
 			}
 
 			conn, err := fakeRcvr.Accept()
diff --git a/loggers/fakelogger.go b/loggers/fakelogger.go
index ea2411c4..970c963d 100644
--- a/loggers/fakelogger.go
+++ b/loggers/fakelogger.go
@@ -3,6 +3,7 @@ package loggers
 import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 )
 
 type FakeLogger struct {
@@ -22,7 +23,11 @@ func NewFakeLogger() *FakeLogger {
 
 func (c *FakeLogger) GetName() string { return c.name }
 
-func (c *FakeLogger) SetLoggers(loggers []dnsutils.Worker) {}
+func (c *FakeLogger) AddDefaultRoute(wrk pkgutils.Worker) {}
+
+func (c *FakeLogger) AddDroppedRoute(wrk pkgutils.Worker) {}
+
+func (c *FakeLogger) SetLoggers(loggers []pkgutils.Worker) {}
 
 func (c *FakeLogger) ReadConfig() {}
 
@@ -30,7 +35,7 @@ func (c *FakeLogger) ReloadConfig(config *pkgconfig.Config) {}
 
 func (c *FakeLogger) Stop() {}
 
-func (c *FakeLogger) Channel() chan dnsutils.DNSMessage {
+func (c *FakeLogger) GetInputChannel() chan dnsutils.DNSMessage {
 	return c.inputChan
 }
 
diff --git a/loggers/falco.go b/loggers/falco.go
index dd634d3c..32375da3 100644
--- a/loggers/falco.go
+++ b/loggers/falco.go
@@ -8,160 +8,182 @@ import (
 
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/transformers"
 	"github.com/dmachard/go-logger"
 )
 
 type FalcoClient struct {
-	stopProcess chan bool
-	doneProcess chan bool
-	stopRun     chan bool
-	doneRun     chan bool
-	inputChan   chan dnsutils.DNSMessage
-	outputChan  chan dnsutils.DNSMessage
-	config      *pkgconfig.Config
-	configChan  chan *pkgconfig.Config
-	logger      *logger.Logger
-	name        string
-	url         string
+	stopProcess    chan bool
+	doneProcess    chan bool
+	stopRun        chan bool
+	doneRun        chan bool
+	inputChan      chan dnsutils.DNSMessage
+	outputChan     chan dnsutils.DNSMessage
+	config         *pkgconfig.Config
+	configChan     chan *pkgconfig.Config
+	logger         *logger.Logger
+	name           string
+	url            string
+	RoutingHandler pkgutils.RoutingHandler
 }
 
 func NewFalcoClient(config *pkgconfig.Config, console *logger.Logger, name string) *FalcoClient {
 	console.Info("[%s] logger=falco - enabled", name)
-	f := &FalcoClient{
-		stopProcess: make(chan bool),
-		doneProcess: make(chan bool),
-		stopRun:     make(chan bool),
-		doneRun:     make(chan bool),
-		inputChan:   make(chan dnsutils.DNSMessage, config.Loggers.FalcoClient.ChannelBufferSize),
-		outputChan:  make(chan dnsutils.DNSMessage, config.Loggers.FalcoClient.ChannelBufferSize),
-		logger:      console,
-		config:      config,
-		configChan:  make(chan *pkgconfig.Config),
-		name:        name,
+	fc := &FalcoClient{
+		stopProcess:    make(chan bool),
+		doneProcess:    make(chan bool),
+		stopRun:        make(chan bool),
+		doneRun:        make(chan bool),
+		inputChan:      make(chan dnsutils.DNSMessage, config.Loggers.FalcoClient.ChannelBufferSize),
+		outputChan:     make(chan dnsutils.DNSMessage, config.Loggers.FalcoClient.ChannelBufferSize),
+		logger:         console,
+		config:         config,
+		configChan:     make(chan *pkgconfig.Config),
+		name:           name,
+		RoutingHandler: pkgutils.NewRoutingHandler(config, console, name),
 	}
-	f.ReadConfig()
-	return f
+	fc.ReadConfig()
+	return fc
 }
 
-func (f *FalcoClient) GetName() string { return f.name }
+func (fc *FalcoClient) GetName() string { return fc.name }
 
-func (f *FalcoClient) SetLoggers(loggers []dnsutils.Worker) {}
+func (fc *FalcoClient) AddDroppedRoute(wrk pkgutils.Worker) {
+	fc.RoutingHandler.AddDroppedRoute(wrk)
+}
+
+func (fc *FalcoClient) AddDefaultRoute(wrk pkgutils.Worker) {
+	fc.RoutingHandler.AddDefaultRoute(wrk)
+}
+
+func (fc *FalcoClient) SetLoggers(loggers []pkgutils.Worker) {}
 
-func (f *FalcoClient) ReadConfig() {
-	f.url = f.config.Loggers.FalcoClient.URL
+func (fc *FalcoClient) ReadConfig() {
+	fc.url = fc.config.Loggers.FalcoClient.URL
 }
 
-func (f *FalcoClient) ReloadConfig(config *pkgconfig.Config) {
-	f.LogInfo("reload configuration!")
-	f.configChan <- config
+func (fc *FalcoClient) ReloadConfig(config *pkgconfig.Config) {
+	fc.LogInfo("reload configuration!")
+	fc.configChan <- config
 }
 
-func (f *FalcoClient) Channel() chan dnsutils.DNSMessage {
-	return f.inputChan
+func (fc *FalcoClient) GetInputChannel() chan dnsutils.DNSMessage {
+	return fc.inputChan
 }
 
-func (f *FalcoClient) LogInfo(msg string, v ...interface{}) {
-	f.logger.Info("["+f.name+"] logger=falco - "+msg, v...)
+func (fc *FalcoClient) LogInfo(msg string, v ...interface{}) {
+	fc.logger.Info("["+fc.name+"] logger=falco - "+msg, v...)
 }
 
-func (f *FalcoClient) LogError(msg string, v ...interface{}) {
-	f.logger.Error("["+f.name+"] logger=falco - "+msg, v...)
+func (fc *FalcoClient) LogError(msg string, v ...interface{}) {
+	fc.logger.Error("["+fc.name+"] logger=falco - "+msg, v...)
 }
 
-func (f *FalcoClient) Stop() {
-	f.LogInfo("stopping to run...")
-	f.stopRun <- true
-	<-f.doneRun
+func (fc *FalcoClient) Stop() {
+	fc.LogInfo("stopping routing handler...")
+	fc.RoutingHandler.Stop()
 
-	f.LogInfo("stopping to process...")
-	f.stopProcess <- true
-	<-f.doneProcess
+	fc.LogInfo("stopping to run...")
+	fc.stopRun <- true
+	<-fc.doneRun
+
+	fc.LogInfo("stopping to process...")
+	fc.stopProcess <- true
+	<-fc.doneProcess
 }
 
-func (f *FalcoClient) Run() {
-	f.LogInfo("running in background...")
+func (fc *FalcoClient) Run() {
+	fc.LogInfo("running in background...")
+
+	// prepare next channels
+	defaultRoutes, defaultNames := fc.RoutingHandler.GetDefaultRoutes()
+	droppedRoutes, droppedNames := fc.RoutingHandler.GetDroppedRoutes()
 
 	// prepare transforms
 	listChannel := []chan dnsutils.DNSMessage{}
-	listChannel = append(listChannel, f.outputChan)
-	subprocessors := transformers.NewTransforms(&f.config.OutgoingTransformers, f.logger, f.name, listChannel, 0)
+	listChannel = append(listChannel, fc.outputChan)
+	subprocessors := transformers.NewTransforms(&fc.config.OutgoingTransformers, fc.logger, fc.name, listChannel, 0)
 
 	// goroutine to process transformed dns messages
-	go f.Process()
+	go fc.Process()
 
 	// loop to process incoming messages
 RUN_LOOP:
 	for {
 		select {
-		case <-f.stopRun:
+		case <-fc.stopRun:
 			// cleanup transformers
 			subprocessors.Reset()
 
-			f.doneRun <- true
+			fc.doneRun <- true
 			break RUN_LOOP
 
-		case cfg, opened := <-f.configChan:
+		case cfg, opened := <-fc.configChan:
 			if !opened {
 				return
 			}
-			f.config = cfg
-			f.ReadConfig()
+			fc.config = cfg
+			fc.ReadConfig()
 			subprocessors.ReloadConfig(&cfg.OutgoingTransformers)
 
-		case dm, opened := <-f.inputChan:
+		case dm, opened := <-fc.inputChan:
 			if !opened {
-				f.LogInfo("input channel closed!")
+				fc.LogInfo("input channel closed!")
 				return
 			}
 
 			// apply tranforms, init dns message with additionnals parts if necessary
 			subprocessors.InitDNSMessageFormat(&dm)
 			if subprocessors.ProcessMessage(&dm) == transformers.ReturnDrop {
+				fc.RoutingHandler.SendTo(droppedRoutes, droppedNames, dm)
 				continue
 			}
 
+			// send to next ?
+			fc.RoutingHandler.SendTo(defaultRoutes, defaultNames, dm)
+
 			// send to output channel
-			f.outputChan <- dm
+			fc.outputChan <- dm
 		}
 	}
-	f.LogInfo("run terminated")
+	fc.LogInfo("run terminated")
 }
 
-func (f *FalcoClient) Process() {
+func (fc *FalcoClient) Process() {
 	buffer := new(bytes.Buffer)
-	f.LogInfo("ready to process")
+	fc.LogInfo("ready to process")
 
 PROCESS_LOOP:
 	for {
 		select {
-		case <-f.stopProcess:
-			f.doneProcess <- true
+		case <-fc.stopProcess:
+			fc.doneProcess <- true
 			break PROCESS_LOOP
 
 			// incoming dns message to process
-		case dm, opened := <-f.outputChan:
+		case dm, opened := <-fc.outputChan:
 			if !opened {
-				f.LogInfo("output channel closed!")
+				fc.LogInfo("output channel closed!")
 				return
 			}
 
 			// encode
 			json.NewEncoder(buffer).Encode(dm)
 
-			req, _ := http.NewRequest("POST", f.url, buffer)
+			req, _ := http.NewRequest("POST", fc.url, buffer)
 			req.Header.Set("Content-Type", "application/json")
 			client := &http.Client{
 				Timeout: 5 * time.Second,
 			}
 			_, err := client.Do(req)
 			if err != nil {
-				f.LogError(err.Error())
+				fc.LogError(err.Error())
 			}
 
 			// finally reset the buffer for next iter
 			buffer.Reset()
 		}
 	}
-	f.LogInfo("processing terminated")
+	fc.LogInfo("processing terminated")
 }
diff --git a/loggers/falco_test.go b/loggers/falco_test.go
index 88ea3d8b..77056812 100644
--- a/loggers/falco_test.go
+++ b/loggers/falco_test.go
@@ -39,7 +39,7 @@ func Test_FalcoClient(t *testing.T) {
 			go g.Run()
 
 			dm := dnsutils.GetFakeDNSMessage()
-			g.Channel() <- dm
+			g.GetInputChannel() <- dm
 
 			// accept conn
 			conn, err := fakeRcvr.Accept()
diff --git a/loggers/fluentd.go b/loggers/fluentd.go
index 9144f2f8..1f24314a 100644
--- a/loggers/fluentd.go
+++ b/loggers/fluentd.go
@@ -11,6 +11,7 @@ import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/netlib"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/transformers"
 	"github.com/dmachard/go-logger"
 	"github.com/vmihailenco/msgpack"
@@ -34,11 +35,12 @@ type FluentdClient struct {
 	transportReconnect chan bool
 	writerReady        bool
 	name               string
+	RoutingHandler     pkgutils.RoutingHandler
 }
 
 func NewFluentdClient(config *pkgconfig.Config, logger *logger.Logger, name string) *FluentdClient {
 	logger.Info("[%s] logger=fluentd - enabled", name)
-	s := &FluentdClient{
+	fc := &FluentdClient{
 		stopProcess:        make(chan bool),
 		doneProcess:        make(chan bool),
 		stopRun:            make(chan bool),
@@ -53,130 +55,140 @@ func NewFluentdClient(config *pkgconfig.Config, logger *logger.Logger, name stri
 		config:             config,
 		configChan:         make(chan *pkgconfig.Config),
 		name:               name,
+		RoutingHandler:     pkgutils.NewRoutingHandler(config, logger, name),
 	}
 
-	s.ReadConfig()
+	fc.ReadConfig()
+	return fc
+}
+
+func (fc *FluentdClient) GetName() string { return fc.name }
 
-	return s
+func (fc *FluentdClient) AddDroppedRoute(wrk pkgutils.Worker) {
+	fc.RoutingHandler.AddDroppedRoute(wrk)
 }
 
-func (c *FluentdClient) GetName() string { return c.name }
+func (fc *FluentdClient) AddDefaultRoute(wrk pkgutils.Worker) {
+	fc.RoutingHandler.AddDefaultRoute(wrk)
+}
 
-func (c *FluentdClient) SetLoggers(loggers []dnsutils.Worker) {}
+func (fc *FluentdClient) SetLoggers(loggers []pkgutils.Worker) {}
 
-func (c *FluentdClient) ReadConfig() {
-	c.transport = c.config.Loggers.Fluentd.Transport
+func (fc *FluentdClient) ReadConfig() {
+	fc.transport = fc.config.Loggers.Fluentd.Transport
 
 	// begin backward compatibility
-	if c.config.Loggers.Fluentd.TLSSupport {
-		c.transport = netlib.SocketTLS
+	if fc.config.Loggers.Fluentd.TLSSupport {
+		fc.transport = netlib.SocketTLS
 	}
-	if len(c.config.Loggers.Fluentd.SockPath) > 0 {
-		c.transport = netlib.SocketUnix
+	if len(fc.config.Loggers.Fluentd.SockPath) > 0 {
+		fc.transport = netlib.SocketUnix
 	}
-	// end
 }
 
-func (c *FluentdClient) ReloadConfig(config *pkgconfig.Config) {
-	c.LogInfo("reload configuration!")
-	c.configChan <- config
+func (fc *FluentdClient) ReloadConfig(config *pkgconfig.Config) {
+	fc.LogInfo("reload configuration!")
+	fc.configChan <- config
 }
 
-func (c *FluentdClient) LogInfo(msg string, v ...interface{}) {
-	c.logger.Info("["+c.name+"] logger=fluentd - "+msg, v...)
+func (fc *FluentdClient) LogInfo(msg string, v ...interface{}) {
+	fc.logger.Info("["+fc.name+"] logger=fluentd - "+msg, v...)
 }
 
-func (c *FluentdClient) LogError(msg string, v ...interface{}) {
-	c.logger.Error("["+c.name+"] logger=fluentd - "+msg, v...)
+func (fc *FluentdClient) LogError(msg string, v ...interface{}) {
+	fc.logger.Error("["+fc.name+"] logger=fluentd - "+msg, v...)
 }
 
-func (c *FluentdClient) Channel() chan dnsutils.DNSMessage {
-	return c.inputChan
+func (fc *FluentdClient) GetInputChannel() chan dnsutils.DNSMessage {
+	return fc.inputChan
 }
 
-func (c *FluentdClient) Stop() {
-	c.LogInfo("stopping to run...")
-	c.stopRun <- true
-	<-c.doneRun
+func (fc *FluentdClient) Stop() {
+	fc.LogInfo("stopping routing handler...")
+	fc.RoutingHandler.Stop()
 
-	c.LogInfo("stopping to read...")
-	c.stopRead <- true
-	<-c.doneRead
+	fc.LogInfo("stopping to run...")
+	fc.stopRun <- true
+	<-fc.doneRun
 
-	c.LogInfo("stopping to process...")
-	c.stopProcess <- true
-	<-c.doneProcess
+	fc.LogInfo("stopping to read...")
+	fc.stopRead <- true
+	<-fc.doneRead
+
+	fc.LogInfo("stopping to process...")
+	fc.stopProcess <- true
+	<-fc.doneProcess
 }
 
-func (c *FluentdClient) Disconnect() {
-	if c.transportConn != nil {
-		c.LogInfo("closing tcp connection")
-		c.transportConn.Close()
+func (fc *FluentdClient) Disconnect() {
+	if fc.transportConn != nil {
+		fc.LogInfo("closing tcp connection")
+		fc.transportConn.Close()
 	}
 }
 
-func (c *FluentdClient) ReadFromConnection() {
+func (fc *FluentdClient) ReadFromConnection() {
 	buffer := make([]byte, 4096)
 
 	go func() {
 		for {
-			_, err := c.transportConn.Read(buffer)
+			_, err := fc.transportConn.Read(buffer)
 			if err != nil {
 				if errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed) {
-					c.LogInfo("read from connection terminated")
+					fc.LogInfo("read from connection terminated")
 					break
 				}
-				c.LogError("Error on reading: %s", err.Error())
+				fc.LogError("Error on reading: %s", err.Error())
 			}
 			// We just discard the data
 		}
 	}()
 
 	// block goroutine until receive true event in stopRead channel
-	<-c.stopRead
-	c.doneRead <- true
+	<-fc.stopRead
+	fc.doneRead <- true
 
-	c.LogInfo("read goroutine terminated")
+	fc.LogInfo("read goroutine terminated")
 }
 
-func (c *FluentdClient) ConnectToRemote() {
+func (fc *FluentdClient) ConnectToRemote() {
 	for {
-		if c.transportConn != nil {
-			c.transportConn.Close()
-			c.transportConn = nil
+		if fc.transportConn != nil {
+			fc.transportConn.Close()
+			fc.transportConn = nil
 		}
 
-		address := c.config.Loggers.Fluentd.RemoteAddress + ":" + strconv.Itoa(c.config.Loggers.Fluentd.RemotePort)
-		connTimeout := time.Duration(c.config.Loggers.Fluentd.ConnectTimeout) * time.Second
+		address := fc.config.Loggers.Fluentd.RemoteAddress + ":" + strconv.Itoa(fc.config.Loggers.Fluentd.RemotePort)
+		connTimeout := time.Duration(fc.config.Loggers.Fluentd.ConnectTimeout) * time.Second
 
 		// make the connection
 		var conn net.Conn
 		var err error
 
-		switch c.transport {
+		switch fc.transport {
 		case netlib.SocketUnix:
-			address = c.config.Loggers.Fluentd.RemoteAddress
-			if len(c.config.Loggers.Fluentd.SockPath) > 0 {
-				address = c.config.Loggers.Fluentd.SockPath
+			address = fc.config.Loggers.Fluentd.RemoteAddress
+			if len(fc.config.Loggers.Fluentd.SockPath) > 0 {
+				address = fc.config.Loggers.Fluentd.SockPath
 			}
-			c.LogInfo("connecting to %s://%s", c.transport, address)
-			conn, err = net.DialTimeout(c.transport, address, connTimeout)
+			fc.LogInfo("connecting to %s://%s", fc.transport, address)
+			conn, err = net.DialTimeout(fc.transport, address, connTimeout)
 
 		case netlib.SocketTCP:
-			c.LogInfo("connecting to %s://%s", c.transport, address)
-			conn, err = net.DialTimeout(c.transport, address, connTimeout)
+			fc.LogInfo("connecting to %s://%s", fc.transport, address)
+			conn, err = net.DialTimeout(fc.transport, address, connTimeout)
 
 		case netlib.SocketTLS:
-			c.LogInfo("connecting to %s://%s", c.transport, address)
+			fc.LogInfo("connecting to %s://%s", fc.transport, address)
 
 			var tlsConfig *tls.Config
 
 			tlsOptions := pkgconfig.TLSOptions{
-				InsecureSkipVerify: c.config.Loggers.Fluentd.TLSInsecure,
-				MinVersion:         c.config.Loggers.Fluentd.TLSMinVersion,
-				CAFile:             c.config.Loggers.Fluentd.CAFile,
-				CertFile:           c.config.Loggers.Fluentd.CertFile,
-				KeyFile:            c.config.Loggers.Fluentd.KeyFile,
+				InsecureSkipVerify: fc.config.Loggers.Fluentd.TLSInsecure,
+				MinVersion:         fc.config.Loggers.Fluentd.TLSMinVersion,
+				CAFile:             fc.config.Loggers.Fluentd.CAFile,
+				CertFile:           fc.config.Loggers.Fluentd.CertFile,
+				KeyFile:            fc.config.Loggers.Fluentd.KeyFile,
 			}
 
 			tlsConfig, err = pkgconfig.TLSClientConfig(tlsOptions)
@@ -185,37 +197,37 @@ func (c *FluentdClient) ConnectToRemote() {
 				conn, err = tls.DialWithDialer(dialer, netlib.SocketTCP, address, tlsConfig)
 			}
 		default:
-			c.logger.Fatal("logger=fluent - invalid transport:", c.transport)
+			fc.logger.Fatal("logger=fluent - invalid transport:", fc.transport)
 		}
 
 		// something is wrong during connection ?
 		if err != nil {
-			c.LogError("connect error: %s", err)
-			c.LogInfo("retry to connect in %d seconds", c.config.Loggers.Fluentd.RetryInterval)
-			time.Sleep(time.Duration(c.config.Loggers.Fluentd.RetryInterval) * time.Second)
+			fc.LogError("connect error: %s", err)
+			fc.LogInfo("retry to connect in %d seconds", fc.config.Loggers.Fluentd.RetryInterval)
+			time.Sleep(time.Duration(fc.config.Loggers.Fluentd.RetryInterval) * time.Second)
 			continue
 		}
 
-		c.transportConn = conn
+		fc.transportConn = conn
 
 		// block until framestream is ready
-		c.transportReady <- true
+		fc.transportReady <- true
 
 		// block until an error occured, need to reconnect
-		c.transportReconnect <- true
+		fc.transportReconnect <- true
 	}
 }
 
-func (c *FluentdClient) FlushBuffer(buf *[]dnsutils.DNSMessage) {
+func (fc *FluentdClient) FlushBuffer(buf *[]dnsutils.DNSMessage) {
 
-	tag, _ := msgpack.Marshal(c.config.Loggers.Fluentd.Tag)
+	tag, _ := msgpack.Marshal(fc.config.Loggers.Fluentd.Tag)
 
 	for _, dm := range *buf {
 		// prepare event
 		tm, _ := msgpack.Marshal(dm.DNSTap.TimeSec)
 		record, err := msgpack.Marshal(dm)
 		if err != nil {
-			c.LogError("msgpack error:", err.Error())
+			fc.LogError("msgpack error:", err.Error())
 			continue
 		}
 
@@ -229,104 +241,112 @@ func (c *FluentdClient) FlushBuffer(buf *[]dnsutils.DNSMessage) {
 		encoded = append(encoded, record...)
 
 		// write event message
-		_, err = c.transportConn.Write(encoded)
+		_, err = fc.transportConn.Write(encoded)
 
 		// flusth the buffer
 		if err != nil {
-			c.LogError("send transport error", err.Error())
-			c.writerReady = false
-			<-c.transportReconnect
+			fc.LogError("send transport error", err.Error())
+			fc.writerReady = false
+			<-fc.transportReconnect
 			break
 		}
 	}
 }
 
-func (c *FluentdClient) Run() {
-	c.LogInfo("running in background...")
+func (fc *FluentdClient) Run() {
+	fc.LogInfo("running in background...")
+
+	// prepare next channels
+	defaultRoutes, defaultNames := fc.RoutingHandler.GetDefaultRoutes()
+	droppedRoutes, droppedNames := fc.RoutingHandler.GetDroppedRoutes()
 
 	// prepare transforms
 	listChannel := []chan dnsutils.DNSMessage{}
-	listChannel = append(listChannel, c.outputChan)
-	subprocessors := transformers.NewTransforms(&c.config.OutgoingTransformers, c.logger, c.name, listChannel, 0)
+	listChannel = append(listChannel, fc.outputChan)
+	subprocessors := transformers.NewTransforms(&fc.config.OutgoingTransformers, fc.logger, fc.name, listChannel, 0)
 
 	// goroutine to process transformed dns messages
-	go c.Process()
+	go fc.Process()
 
 	// init remote conn
-	go c.ConnectToRemote()
+	go fc.ConnectToRemote()
 
 	// loop to process incoming messages
 RUN_LOOP:
 	for {
 		select {
-		case <-c.stopRun:
+		case <-fc.stopRun:
 			// cleanup transformers
 			subprocessors.Reset()
 
-			c.doneRun <- true
+			fc.doneRun <- true
 			break RUN_LOOP
 
-		case cfg, opened := <-c.configChan:
+		case cfg, opened := <-fc.configChan:
 			if !opened {
 				return
 			}
-			c.config = cfg
-			c.ReadConfig()
+			fc.config = cfg
+			fc.ReadConfig()
 			subprocessors.ReloadConfig(&cfg.OutgoingTransformers)
 
-		case dm, opened := <-c.inputChan:
+		case dm, opened := <-fc.inputChan:
 			if !opened {
-				c.LogInfo("input channel closed!")
+				fc.LogInfo("input channel closed!")
 				return
 			}
 
 			// apply tranforms, init dns message with additionnals parts if necessary
 			subprocessors.InitDNSMessageFormat(&dm)
 			if subprocessors.ProcessMessage(&dm) == transformers.ReturnDrop {
+				fc.RoutingHandler.SendTo(droppedRoutes, droppedNames, dm)
 				continue
 			}
 
+			// send to next ?
+			fc.RoutingHandler.SendTo(defaultRoutes, defaultNames, dm)
+
 			// send to output channel
-			c.outputChan <- dm
+			fc.outputChan <- dm
 		}
 	}
-	c.LogInfo("run terminated")
+	fc.LogInfo("run terminated")
 }
 
-func (c *FluentdClient) Process() {
+func (fc *FluentdClient) Process() {
 	// init buffer
 	bufferDm := []dnsutils.DNSMessage{}
 
 	// init flust timer for buffer
-	flushInterval := time.Duration(c.config.Loggers.Fluentd.FlushInterval) * time.Second
+	flushInterval := time.Duration(fc.config.Loggers.Fluentd.FlushInterval) * time.Second
 	flushTimer := time.NewTimer(flushInterval)
 
-	c.LogInfo("ready to process")
+	fc.LogInfo("ready to process")
 
 PROCESS_LOOP:
 	for {
 		select {
-		case <-c.stopProcess:
-			c.doneProcess <- true
+		case <-fc.stopProcess:
+			fc.doneProcess <- true
 			break PROCESS_LOOP
 
-		case <-c.transportReady:
-			c.LogInfo("connected")
-			c.writerReady = true
+		case <-fc.transportReady:
+			fc.LogInfo("connected")
+			fc.writerReady = true
 
 			// read from the connection until we stop
-			go c.ReadFromConnection()
+			go fc.ReadFromConnection()
 
 		// incoming dns message to process
-		case dm, opened := <-c.outputChan:
+		case dm, opened := <-fc.outputChan:
 			if !opened {
-				c.LogInfo("output channel closed!")
+				fc.LogInfo("output channel closed!")
 				return
 			}
 
 			// drop dns message if the connection is not ready to avoid memory leak or
 			// to block the channel
-			if !c.writerReady {
+			if !fc.writerReady {
 				continue
 			}
 
@@ -334,24 +354,24 @@ PROCESS_LOOP:
 			bufferDm = append(bufferDm, dm)
 
 			// buffer is full ?
-			if len(bufferDm) >= c.config.Loggers.Fluentd.BufferSize {
-				c.FlushBuffer(&bufferDm)
+			if len(bufferDm) >= fc.config.Loggers.Fluentd.BufferSize {
+				fc.FlushBuffer(&bufferDm)
 			}
 
 		// flush the buffer
 		case <-flushTimer.C:
-			if !c.writerReady {
+			if !fc.writerReady {
 				bufferDm = nil
 				continue
 			}
 
 			if len(bufferDm) > 0 {
-				c.FlushBuffer(&bufferDm)
+				fc.FlushBuffer(&bufferDm)
 			}
 
 			// restart timer
 			flushTimer.Reset(flushInterval)
 		}
 	}
-	c.LogInfo("processing terminated")
+	fc.LogInfo("processing terminated")
 }
diff --git a/loggers/fluentd_test.go b/loggers/fluentd_test.go
index b8e6ff48..1afe8a7d 100644
--- a/loggers/fluentd_test.go
+++ b/loggers/fluentd_test.go
@@ -50,7 +50,7 @@ func Test_FluentdClient(t *testing.T) {
 			// send fake dns message to logger
 			time.Sleep(time.Second)
 			dm := dnsutils.GetFakeDNSMessage()
-			g.Channel() <- dm
+			g.GetInputChannel() <- dm
 
 			// read data on fake server side
 			buf := make([]byte, 4096)
diff --git a/loggers/influxdb.go b/loggers/influxdb.go
index 4b3eabc3..f3ebb89a 100644
--- a/loggers/influxdb.go
+++ b/loggers/influxdb.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/transformers"
 	"github.com/dmachard/go-logger"
 
@@ -13,173 +14,194 @@ import (
 )
 
 type InfluxDBClient struct {
-	stopProcess  chan bool
-	doneProcess  chan bool
-	stopRun      chan bool
-	doneRun      chan bool
-	inputChan    chan dnsutils.DNSMessage
-	outputChan   chan dnsutils.DNSMessage
-	config       *pkgconfig.Config
-	configChan   chan *pkgconfig.Config
-	logger       *logger.Logger
-	influxdbConn influxdb2.Client
-	writeAPI     api.WriteAPI
-	name         string
+	stopProcess    chan bool
+	doneProcess    chan bool
+	stopRun        chan bool
+	doneRun        chan bool
+	inputChan      chan dnsutils.DNSMessage
+	outputChan     chan dnsutils.DNSMessage
+	config         *pkgconfig.Config
+	configChan     chan *pkgconfig.Config
+	logger         *logger.Logger
+	influxdbConn   influxdb2.Client
+	writeAPI       api.WriteAPI
+	name           string
+	RoutingHandler pkgutils.RoutingHandler
 }
 
 func NewInfluxDBClient(config *pkgconfig.Config, logger *logger.Logger, name string) *InfluxDBClient {
 	logger.Info("[%s] logger=influxdb - enabled", name)
 
-	s := &InfluxDBClient{
-		stopProcess: make(chan bool),
-		doneProcess: make(chan bool),
-		stopRun:     make(chan bool),
-		doneRun:     make(chan bool),
-		inputChan:   make(chan dnsutils.DNSMessage, config.Loggers.InfluxDB.ChannelBufferSize),
-		outputChan:  make(chan dnsutils.DNSMessage, config.Loggers.InfluxDB.ChannelBufferSize),
-		logger:      logger,
-		config:      config,
-		configChan:  make(chan *pkgconfig.Config),
-		name:        name,
+	ic := &InfluxDBClient{
+		stopProcess:    make(chan bool),
+		doneProcess:    make(chan bool),
+		stopRun:        make(chan bool),
+		doneRun:        make(chan bool),
+		inputChan:      make(chan dnsutils.DNSMessage, config.Loggers.InfluxDB.ChannelBufferSize),
+		outputChan:     make(chan dnsutils.DNSMessage, config.Loggers.InfluxDB.ChannelBufferSize),
+		logger:         logger,
+		config:         config,
+		configChan:     make(chan *pkgconfig.Config),
+		name:           name,
+		RoutingHandler: pkgutils.NewRoutingHandler(config, logger, name),
 	}
 
-	s.ReadConfig()
+	ic.ReadConfig()
 
-	return s
+	return ic
 }
 
-func (i *InfluxDBClient) GetName() string { return i.name }
+func (ic *InfluxDBClient) GetName() string { return ic.name }
 
-func (i *InfluxDBClient) SetLoggers(loggers []dnsutils.Worker) {}
+func (ic *InfluxDBClient) AddDroppedRoute(wrk pkgutils.Worker) {
+	ic.RoutingHandler.AddDroppedRoute(wrk)
+}
+
+func (ic *InfluxDBClient) AddDefaultRoute(wrk pkgutils.Worker) {
+	ic.RoutingHandler.AddDefaultRoute(wrk)
+}
+
+func (ic *InfluxDBClient) SetLoggers(loggers []pkgutils.Worker) {}
 
-func (i *InfluxDBClient) ReadConfig() {}
+func (ic *InfluxDBClient) ReadConfig() {}
 
-func (i *InfluxDBClient) ReloadConfig(config *pkgconfig.Config) {
-	i.LogInfo("reload configuration!")
-	i.configChan <- config
+func (ic *InfluxDBClient) ReloadConfig(config *pkgconfig.Config) {
+	ic.LogInfo("reload configuration!")
+	ic.configChan <- config
 }
 
-func (i *InfluxDBClient) LogInfo(msg string, v ...interface{}) {
-	i.logger.Info("["+i.name+"] logger=influxdb - "+msg, v...)
+func (ic *InfluxDBClient) LogInfo(msg string, v ...interface{}) {
+	ic.logger.Info("["+ic.name+"] logger=influxdb - "+msg, v...)
 }
 
-func (i *InfluxDBClient) LogError(msg string, v ...interface{}) {
-	i.logger.Error("["+i.name+"] logger=influxdb - "+msg, v...)
+func (ic *InfluxDBClient) LogError(msg string, v ...interface{}) {
+	ic.logger.Error("["+ic.name+"] logger=influxdb - "+msg, v...)
 }
 
-func (i *InfluxDBClient) Channel() chan dnsutils.DNSMessage {
-	return i.inputChan
+func (ic *InfluxDBClient) GetInputChannel() chan dnsutils.DNSMessage {
+	return ic.inputChan
 }
 
-func (i *InfluxDBClient) Stop() {
-	i.LogInfo("stopping to run...")
-	i.stopRun <- true
-	<-i.doneRun
+func (ic *InfluxDBClient) Stop() {
+	ic.LogInfo("stopping routing handler...")
+	ic.RoutingHandler.Stop()
 
-	i.LogInfo("stopping to process...")
-	i.stopProcess <- true
-	<-i.doneProcess
+	ic.LogInfo("stopping to run...")
+	ic.stopRun <- true
+	<-ic.doneRun
+
+	ic.LogInfo("stopping to process...")
+	ic.stopProcess <- true
+	<-ic.doneProcess
 }
 
-func (i *InfluxDBClient) Run() {
-	i.LogInfo("running in background...")
+func (ic *InfluxDBClient) Run() {
+	ic.LogInfo("running in background...")
+
+	// prepare next channels
+	defaultRoutes, defaultNames := ic.RoutingHandler.GetDefaultRoutes()
+	droppedRoutes, droppedNames := ic.RoutingHandler.GetDroppedRoutes()
 
 	// prepare transforms
 	listChannel := []chan dnsutils.DNSMessage{}
-	listChannel = append(listChannel, i.outputChan)
-	subprocessors := transformers.NewTransforms(&i.config.OutgoingTransformers, i.logger, i.name, listChannel, 0)
+	listChannel = append(listChannel, ic.outputChan)
+	subprocessors := transformers.NewTransforms(&ic.config.OutgoingTransformers, ic.logger, ic.name, listChannel, 0)
 
 	// goroutine to process transformed dns messages
-	go i.Process()
+	go ic.Process()
 
 	// loop to process incoming messages
 RUN_LOOP:
 	for {
 		select {
-		case <-i.stopRun:
+		case <-ic.stopRun:
 			// cleanup transformers
 			subprocessors.Reset()
 
-			i.doneRun <- true
+			ic.doneRun <- true
 			break RUN_LOOP
 
-		case cfg, opened := <-i.configChan:
+		case cfg, opened := <-ic.configChan:
 			if !opened {
 				return
 			}
-			i.config = cfg
-			i.ReadConfig()
+			ic.config = cfg
+			ic.ReadConfig()
 			subprocessors.ReloadConfig(&cfg.OutgoingTransformers)
 
-		case dm, opened := <-i.inputChan:
+		case dm, opened := <-ic.inputChan:
 			if !opened {
-				i.LogInfo("input channel closed!")
+				ic.LogInfo("input channel closed!")
 				return
 			}
 
 			// apply tranforms, init dns message with additionnals parts if necessary
 			subprocessors.InitDNSMessageFormat(&dm)
 			if subprocessors.ProcessMessage(&dm) == transformers.ReturnDrop {
+				ic.RoutingHandler.SendTo(droppedRoutes, droppedNames, dm)
 				continue
 			}
 
+			// send to next ?
+			ic.RoutingHandler.SendTo(defaultRoutes, defaultNames, dm)
+
 			// send to output channel
-			i.outputChan <- dm
+			ic.outputChan <- dm
 		}
 	}
-	i.LogInfo("run terminated")
+	ic.LogInfo("run terminated")
 }
 
-func (i *InfluxDBClient) Process() {
+func (ic *InfluxDBClient) Process() {
 	// prepare options for influxdb
 	opts := influxdb2.DefaultOptions()
 	opts.SetUseGZip(true)
-	if i.config.Loggers.InfluxDB.TLSSupport {
+	if ic.config.Loggers.InfluxDB.TLSSupport {
 		tlsOptions := pkgconfig.TLSOptions{
-			InsecureSkipVerify: i.config.Loggers.InfluxDB.TLSInsecure,
-			MinVersion:         i.config.Loggers.InfluxDB.TLSMinVersion,
-			CAFile:             i.config.Loggers.InfluxDB.CAFile,
-			CertFile:           i.config.Loggers.InfluxDB.CertFile,
-			KeyFile:            i.config.Loggers.InfluxDB.KeyFile,
+			InsecureSkipVerify: ic.config.Loggers.InfluxDB.TLSInsecure,
+			MinVersion:         ic.config.Loggers.InfluxDB.TLSMinVersion,
+			CAFile:             ic.config.Loggers.InfluxDB.CAFile,
+			CertFile:           ic.config.Loggers.InfluxDB.CertFile,
+			KeyFile:            ic.config.Loggers.InfluxDB.KeyFile,
 		}
 
 		tlsConfig, err := pkgconfig.TLSClientConfig(tlsOptions)
 		if err != nil {
-			i.logger.Fatal("logger=influxdb - tls config failed:", err)
+			ic.logger.Fatal("logger=influxdb - tls config failed:", err)
 		}
 
 		opts.SetTLSConfig(tlsConfig)
 	}
 	// init the client
 	influxClient := influxdb2.NewClientWithOptions(
-		i.config.Loggers.InfluxDB.ServerURL,
-		i.config.Loggers.InfluxDB.AuthToken,
+		ic.config.Loggers.InfluxDB.ServerURL,
+		ic.config.Loggers.InfluxDB.AuthToken,
 		opts,
 	)
 
 	writeAPI := influxClient.WriteAPI(
-		i.config.Loggers.InfluxDB.Organization,
-		i.config.Loggers.InfluxDB.Bucket,
+		ic.config.Loggers.InfluxDB.Organization,
+		ic.config.Loggers.InfluxDB.Bucket,
 	)
 
-	i.influxdbConn = influxClient
-	i.writeAPI = writeAPI
+	ic.influxdbConn = influxClient
+	ic.writeAPI = writeAPI
 
-	i.LogInfo("ready to process")
+	ic.LogInfo("ready to process")
 PROCESS_LOOP:
 	for {
 		select {
-		case <-i.stopProcess:
+		case <-ic.stopProcess:
 			// Force all unwritten data to be sent
-			i.writeAPI.Flush()
+			ic.writeAPI.Flush()
 			// Ensures background processes finishes
-			i.influxdbConn.Close()
-			i.doneProcess <- true
+			ic.influxdbConn.Close()
+			ic.doneProcess <- true
 			break PROCESS_LOOP
 		// incoming dns message to process
-		case dm, opened := <-i.outputChan:
+		case dm, opened := <-ic.outputChan:
 			if !opened {
-				i.LogInfo("output channel closed!")
+				ic.LogInfo("output channel closed!")
 				return
 			}
 
@@ -195,8 +217,8 @@ PROCESS_LOOP:
 				SetTime(time.Unix(int64(dm.DNSTap.TimeSec), int64(dm.DNSTap.TimeNsec)))
 
 			// write asynchronously
-			i.writeAPI.WritePoint(p)
+			ic.writeAPI.WritePoint(p)
 		}
 	}
-	i.LogInfo("processing terminated")
+	ic.LogInfo("processing terminated")
 }
diff --git a/loggers/influxdb_test.go b/loggers/influxdb_test.go
index eb0e346f..78233cbf 100644
--- a/loggers/influxdb_test.go
+++ b/loggers/influxdb_test.go
@@ -29,7 +29,7 @@ func Test_InfluxDB(t *testing.T) {
 
 	// send fake dns message to logger
 	dm := dnsutils.GetFakeDNSMessage()
-	g.Channel() <- dm
+	g.GetInputChannel() <- dm
 
 	// accept conn
 	conn, err := fakeRcvr.Accept()
diff --git a/loggers/kafkaproducer.go b/loggers/kafkaproducer.go
index b11e3ec8..89d5ecb3 100644
--- a/loggers/kafkaproducer.go
+++ b/loggers/kafkaproducer.go
@@ -11,6 +11,7 @@ import (
 
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/transformers"
 	"github.com/dmachard/go-logger"
 	"github.com/segmentio/kafka-go"
@@ -36,6 +37,7 @@ type KafkaProducer struct {
 	kafkaReconnect chan bool
 	kafkaConnected bool
 	compressCodec  compress.Codec
+	RoutingHandler pkgutils.RoutingHandler
 }
 
 func NewKafkaProducer(config *pkgconfig.Config, logger *logger.Logger, name string) *KafkaProducer {
@@ -53,16 +55,24 @@ func NewKafkaProducer(config *pkgconfig.Config, logger *logger.Logger, name stri
 		kafkaReady:     make(chan bool),
 		kafkaReconnect: make(chan bool),
 		name:           name,
+		RoutingHandler: pkgutils.NewRoutingHandler(config, logger, name),
 	}
 
 	k.ReadConfig()
-
 	return k
 }
 
 func (k *KafkaProducer) GetName() string { return k.name }
 
-func (k *KafkaProducer) SetLoggers(loggers []dnsutils.Worker) {}
+func (k *KafkaProducer) AddDroppedRoute(wrk pkgutils.Worker) {
+	k.RoutingHandler.AddDroppedRoute(wrk)
+}
+
+func (k *KafkaProducer) AddDefaultRoute(wrk pkgutils.Worker) {
+	k.RoutingHandler.AddDefaultRoute(wrk)
+}
+
+func (k *KafkaProducer) SetLoggers(loggers []pkgutils.Worker) {}
 
 func (k *KafkaProducer) ReadConfig() {
 	if len(k.config.Loggers.RedisPub.TextFormat) > 0 {
@@ -102,11 +112,14 @@ func (k *KafkaProducer) LogError(msg string, v ...interface{}) {
 	k.logger.Error("["+k.name+"] logger=kafka - "+msg, v...)
 }
 
-func (k *KafkaProducer) Channel() chan dnsutils.DNSMessage {
+func (k *KafkaProducer) GetInputChannel() chan dnsutils.DNSMessage {
 	return k.inputChan
 }
 
 func (k *KafkaProducer) Stop() {
+	k.LogInfo("stopping routing handler...")
+	k.RoutingHandler.Stop()
+
 	k.LogInfo("stopping to run...")
 	k.stopRun <- true
 	<-k.doneRun
@@ -253,6 +266,10 @@ func (k *KafkaProducer) FlushBuffer(buf *[]dnsutils.DNSMessage) {
 func (k *KafkaProducer) Run() {
 	k.LogInfo("running in background...")
 
+	// prepare next channels
+	defaultRoutes, defaultNames := k.RoutingHandler.GetDefaultRoutes()
+	droppedRoutes, droppedNames := k.RoutingHandler.GetDroppedRoutes()
+
 	// prepare transforms
 	listChannel := []chan dnsutils.DNSMessage{}
 	listChannel = append(listChannel, k.outputChan)
@@ -289,9 +306,13 @@ RUN_LOOP:
 			// apply tranforms, init dns message with additionnals parts if necessary
 			subprocessors.InitDNSMessageFormat(&dm)
 			if subprocessors.ProcessMessage(&dm) == transformers.ReturnDrop {
+				k.RoutingHandler.SendTo(droppedRoutes, droppedNames, dm)
 				continue
 			}
 
+			// send to next ?
+			k.RoutingHandler.SendTo(defaultRoutes, defaultNames, dm)
+
 			// send to output channel
 			k.outputChan <- dm
 		}
diff --git a/loggers/kafkaproducer_test.go b/loggers/kafkaproducer_test.go
index 23a538ce..f333afd4 100644
--- a/loggers/kafkaproducer_test.go
+++ b/loggers/kafkaproducer_test.go
@@ -90,7 +90,7 @@ func Test_KafkaProducer(t *testing.T) {
 
 			// send fake dns message to logger
 			dm := dnsutils.GetFakeDNSMessage()
-			g.Channel() <- dm
+			g.GetInputChannel() <- dm
 
 			// just wait
 			time.Sleep(1 * time.Second)
diff --git a/loggers/logfile.go b/loggers/logfile.go
index 211cac59..f573b715 100644
--- a/loggers/logfile.go
+++ b/loggers/logfile.go
@@ -18,6 +18,7 @@ import (
 
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/transformers"
 	"github.com/dmachard/go-logger"
 	"github.com/google/gopacket"
@@ -66,88 +67,101 @@ type LogFile struct {
 	commpressTimer *time.Timer
 	textFormat     []string
 	name           string
+	RoutingHandler pkgutils.RoutingHandler
 }
 
 func NewLogFile(config *pkgconfig.Config, logger *logger.Logger, name string) *LogFile {
 	logger.Info("[%s] logger=file - enabled", name)
-	l := &LogFile{
-		stopProcess: make(chan bool),
-		doneProcess: make(chan bool),
-		stopRun:     make(chan bool),
-		doneRun:     make(chan bool),
-		inputChan:   make(chan dnsutils.DNSMessage, config.Loggers.LogFile.ChannelBufferSize),
-		outputChan:  make(chan dnsutils.DNSMessage, config.Loggers.LogFile.ChannelBufferSize),
-		config:      config,
-		configChan:  make(chan *pkgconfig.Config),
-		logger:      logger,
-		name:        name,
+	lf := &LogFile{
+		stopProcess:    make(chan bool),
+		doneProcess:    make(chan bool),
+		stopRun:        make(chan bool),
+		doneRun:        make(chan bool),
+		inputChan:      make(chan dnsutils.DNSMessage, config.Loggers.LogFile.ChannelBufferSize),
+		outputChan:     make(chan dnsutils.DNSMessage, config.Loggers.LogFile.ChannelBufferSize),
+		config:         config,
+		configChan:     make(chan *pkgconfig.Config),
+		logger:         logger,
+		name:           name,
+		RoutingHandler: pkgutils.NewRoutingHandler(config, logger, name),
 	}
 
-	l.ReadConfig()
+	lf.ReadConfig()
 
-	if err := l.OpenFile(); err != nil {
-		l.logger.Fatal("["+name+"] logger=file - unable to open output file:", err)
+	if err := lf.OpenFile(); err != nil {
+		lf.logger.Fatal("["+name+"] logger=file - unable to open output file:", err)
 	}
 
-	return l
+	return lf
 }
 
-func (l *LogFile) GetName() string { return l.name }
+func (lf *LogFile) GetName() string { return lf.name }
 
-func (l *LogFile) SetLoggers(loggers []dnsutils.Worker) {}
+func (lf *LogFile) AddDroppedRoute(wrk pkgutils.Worker) {
+	lf.RoutingHandler.AddDroppedRoute(wrk)
+}
+
+func (lf *LogFile) AddDefaultRoute(wrk pkgutils.Worker) {
+	lf.RoutingHandler.AddDefaultRoute(wrk)
+}
+
+func (lf *LogFile) SetLoggers(loggers []pkgutils.Worker) {}
 
-func (l *LogFile) Channel() chan dnsutils.DNSMessage {
-	return l.inputChan
+func (lf *LogFile) GetInputChannel() chan dnsutils.DNSMessage {
+	return lf.inputChan
 }
 
-func (l *LogFile) ReadConfig() {
-	if !IsValidMode(l.config.Loggers.LogFile.Mode) {
-		l.logger.Fatal("["+l.name+"] logger=file - invalid mode: ", l.config.Loggers.LogFile.Mode)
+func (lf *LogFile) ReadConfig() {
+	if !IsValidMode(lf.config.Loggers.LogFile.Mode) {
+		lf.logger.Fatal("["+lf.name+"] logger=file - invalid mode: ", lf.config.Loggers.LogFile.Mode)
 	}
-	l.fileDir = filepath.Dir(l.config.Loggers.LogFile.FilePath)
-	l.fileName = filepath.Base(l.config.Loggers.LogFile.FilePath)
-	l.fileExt = filepath.Ext(l.fileName)
-	l.filePrefix = strings.TrimSuffix(l.fileName, l.fileExt)
+	lf.fileDir = filepath.Dir(lf.config.Loggers.LogFile.FilePath)
+	lf.fileName = filepath.Base(lf.config.Loggers.LogFile.FilePath)
+	lf.fileExt = filepath.Ext(lf.fileName)
+	lf.filePrefix = strings.TrimSuffix(lf.fileName, lf.fileExt)
 
-	if len(l.config.Loggers.LogFile.TextFormat) > 0 {
-		l.textFormat = strings.Fields(l.config.Loggers.LogFile.TextFormat)
+	if len(lf.config.Loggers.LogFile.TextFormat) > 0 {
+		lf.textFormat = strings.Fields(lf.config.Loggers.LogFile.TextFormat)
 	} else {
-		l.textFormat = strings.Fields(l.config.Global.TextFormat)
+		lf.textFormat = strings.Fields(lf.config.Global.TextFormat)
 	}
 
-	l.LogInfo("running in mode: %s", l.config.Loggers.LogFile.Mode)
+	lf.LogInfo("running in mode: %s", lf.config.Loggers.LogFile.Mode)
 }
 
-func (l *LogFile) ReloadConfig(config *pkgconfig.Config) {
-	l.LogInfo("reload configuration!")
-	l.configChan <- config
+func (lf *LogFile) ReloadConfig(config *pkgconfig.Config) {
+	lf.LogInfo("reload configuration!")
+	lf.configChan <- config
 }
 
-func (l *LogFile) LogInfo(msg string, v ...interface{}) {
-	l.logger.Info("["+l.name+"] logger=file - "+msg, v...)
+func (lf *LogFile) LogInfo(msg string, v ...interface{}) {
+	lf.logger.Info("["+lf.name+"] logger=file - "+msg, v...)
 }
 
-func (l *LogFile) LogError(msg string, v ...interface{}) {
-	l.logger.Error("["+l.name+"] logger=file - "+msg, v...)
+func (lf *LogFile) LogError(msg string, v ...interface{}) {
+	lf.logger.Error("["+lf.name+"] logger=file - "+msg, v...)
 }
 
-func (l *LogFile) Stop() {
-	l.LogInfo("stopping to run...")
-	l.stopRun <- true
-	<-l.doneRun
+func (lf *LogFile) Stop() {
+	lf.LogInfo("stopping routing handler...")
+	lf.RoutingHandler.Stop()
 
-	l.LogInfo("stopping to process...")
-	l.stopProcess <- true
-	<-l.doneProcess
+	lf.LogInfo("stopping to run...")
+	lf.stopRun <- true
+	<-lf.doneRun
+
+	lf.LogInfo("stopping to process...")
+	lf.stopProcess <- true
+	<-lf.doneProcess
 }
 
-func (l *LogFile) Cleanup() error {
-	if l.config.Loggers.LogFile.MaxFiles == 0 {
+func (lf *LogFile) Cleanup() error {
+	if lf.config.Loggers.LogFile.MaxFiles == 0 {
 		return nil
 	}
 
 	// remove old files ? keep only max files number
-	entries, err := os.ReadDir(l.fileDir)
+	entries, err := os.ReadDir(lf.fileDir)
 	if err != nil {
 		return err
 	}
@@ -159,7 +173,7 @@ func (l *LogFile) Cleanup() error {
 		}
 
 		// extract timestamp from filename
-		re := regexp.MustCompile(`^` + l.filePrefix + `-(?P<ts>\d+)` + l.fileExt)
+		re := regexp.MustCompile(`^` + lf.filePrefix + `-(?P<ts>\d+)` + lf.fileExt)
 		matches := re.FindStringSubmatch(entry.Name())
 
 		if len(matches) == 0 {
@@ -177,13 +191,13 @@ func (l *LogFile) Cleanup() error {
 	sort.Ints(logFiles)
 
 	// too much log files ?
-	diffNB := len(logFiles) - l.config.Loggers.LogFile.MaxFiles
+	diffNB := len(logFiles) - lf.config.Loggers.LogFile.MaxFiles
 	if diffNB > 0 {
 		for i := 0; i < diffNB; i++ {
-			filename := fmt.Sprintf("%s-%d%s", l.filePrefix, logFiles[i], l.fileExt)
-			f := filepath.Join(l.fileDir, filename)
+			filename := fmt.Sprintf("%s-%d%s", lf.filePrefix, logFiles[i], lf.fileExt)
+			f := filepath.Join(lf.fileDir, filename)
 			if _, err := os.Stat(f); os.IsNotExist(err) {
-				f = filepath.Join(l.fileDir, filename+compressSuffix)
+				f = filepath.Join(lf.fileDir, filename+compressSuffix)
 			}
 
 			// ignore errors on deletion
@@ -194,55 +208,55 @@ func (l *LogFile) Cleanup() error {
 	return nil
 }
 
-func (l *LogFile) OpenFile() error {
+func (lf *LogFile) OpenFile() error {
 
-	fd, err := os.OpenFile(l.config.Loggers.LogFile.FilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
+	fd, err := os.OpenFile(lf.config.Loggers.LogFile.FilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
 	if err != nil {
 		return err
 	}
-	l.fileFd = fd
+	lf.fileFd = fd
 
-	fileinfo, err := os.Stat(l.config.Loggers.LogFile.FilePath)
+	fileinfo, err := os.Stat(lf.config.Loggers.LogFile.FilePath)
 	if err != nil {
 		return err
 	}
 
-	l.fileSize = fileinfo.Size()
+	lf.fileSize = fileinfo.Size()
 
-	switch l.config.Loggers.LogFile.Mode {
+	switch lf.config.Loggers.LogFile.Mode {
 	case pkgconfig.ModeText, pkgconfig.ModeJSON, pkgconfig.ModeFlatJSON:
 		bufferSize := 4096
-		l.writerPlain = bufio.NewWriterSize(fd, bufferSize)
+		lf.writerPlain = bufio.NewWriterSize(fd, bufferSize)
 
 	case pkgconfig.ModePCAP:
-		l.writerPcap = pcapgo.NewWriter(fd)
-		if l.fileSize == 0 {
-			if err := l.writerPcap.WriteFileHeader(65536, layers.LinkTypeEthernet); err != nil {
+		lf.writerPcap = pcapgo.NewWriter(fd)
+		if lf.fileSize == 0 {
+			if err := lf.writerPcap.WriteFileHeader(65536, layers.LinkTypeEthernet); err != nil {
 				return err
 			}
 		}
 
 	case pkgconfig.ModeDNSTap:
 		fsOptions := &framestream.EncoderOptions{ContentType: []byte("protobuf:dnstap.Dnstap"), Bidirectional: false}
-		l.writerDnstap, err = framestream.NewEncoder(fd, fsOptions)
+		lf.writerDnstap, err = framestream.NewEncoder(fd, fsOptions)
 		if err != nil {
 			return err
 		}
 
 	}
 
-	l.LogInfo("file opened with success: %s", l.config.Loggers.LogFile.FilePath)
+	lf.LogInfo("file opened with success: %s", lf.config.Loggers.LogFile.FilePath)
 	return nil
 }
 
-func (l *LogFile) GetMaxSize() int64 {
-	return int64(1024*1024) * int64(l.config.Loggers.LogFile.MaxSize)
+func (lf *LogFile) GetMaxSize() int64 {
+	return int64(1024*1024) * int64(lf.config.Loggers.LogFile.MaxSize)
 }
 
-func (l *LogFile) CompressFile() {
-	entries, err := os.ReadDir(l.fileDir)
+func (lf *LogFile) CompressFile() {
+	entries, err := os.ReadDir(lf.fileDir)
 	if err != nil {
-		l.LogError("unable to list all files: %s", err)
+		lf.LogError("unable to list all files: %s", err)
 		return
 	}
 
@@ -252,155 +266,155 @@ func (l *LogFile) CompressFile() {
 			continue
 		}
 
-		matched, _ := regexp.MatchString(`^`+l.filePrefix+`-\d+`+l.fileExt+`$`, entry.Name())
+		matched, _ := regexp.MatchString(`^`+lf.filePrefix+`-\d+`+lf.fileExt+`$`, entry.Name())
 		if matched {
-			src := filepath.Join(l.fileDir, entry.Name())
-			dst := filepath.Join(l.fileDir, entry.Name()+compressSuffix)
+			src := filepath.Join(lf.fileDir, entry.Name())
+			dst := filepath.Join(lf.fileDir, entry.Name()+compressSuffix)
 
-			fl, err := os.Open(src)
+			fd, err := os.Open(src)
 			if err != nil {
-				l.LogError("compress - failed to open file: ", err)
+				lf.LogError("compress - failed to open file: ", err)
 				continue
 			}
-			defer fl.Close()
+			defer fd.Close()
 
 			fi, err := os.Stat(src)
 			if err != nil {
-				l.LogError("compress - failed to stat file: ", err)
+				lf.LogError("compress - failed to stat file: ", err)
 				continue
 			}
 
 			gzf, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, fi.Mode())
 			if err != nil {
-				l.LogError("compress - failed to open compressed file: ", err)
+				lf.LogError("compress - failed to open compressed file: ", err)
 				continue
 			}
 			defer gzf.Close()
 
 			gz := gzip.NewWriter(gzf)
 
-			if _, err := io.Copy(gz, fl); err != nil {
-				l.LogError("compress - failed to compress file: ", err)
+			if _, err := io.Copy(gz, fd); err != nil {
+				lf.LogError("compress - failed to compress file: ", err)
 				os.Remove(dst)
 				continue
 			}
 			if err := gz.Close(); err != nil {
-				l.LogError("compress - failed to close gz writer: ", err)
+				lf.LogError("compress - failed to close gz writer: ", err)
 				os.Remove(dst)
 				continue
 			}
 			if err := gzf.Close(); err != nil {
-				l.LogError("compress - failed to close gz file: ", err)
+				lf.LogError("compress - failed to close gz file: ", err)
 				os.Remove(dst)
 				continue
 			}
 
-			if err := fl.Close(); err != nil {
-				l.LogError("compress - failed to close log file: ", err)
+			if err := fd.Close(); err != nil {
+				lf.LogError("compress - failed to close log file: ", err)
 				os.Remove(dst)
 				continue
 			}
 			if err := os.Remove(src); err != nil {
-				l.LogError("compress - failed to remove log file: ", err)
+				lf.LogError("compress - failed to remove log file: ", err)
 				os.Remove(dst)
 				continue
 			}
 
 			// post rotate command?
-			l.CompressPostRotateCommand(dst)
+			lf.CompressPostRotateCommand(dst)
 		}
 	}
 
-	l.commpressTimer.Reset(time.Duration(l.config.Loggers.LogFile.CompressInterval) * time.Second)
+	lf.commpressTimer.Reset(time.Duration(lf.config.Loggers.LogFile.CompressInterval) * time.Second)
 }
 
-func (l *LogFile) PostRotateCommand(filename string) {
-	if len(l.config.Loggers.LogFile.PostRotateCommand) > 0 {
-		l.LogInfo("execute postrotate command: %s", filename)
-		_, err := exec.Command(l.config.Loggers.LogFile.PostRotateCommand, filename).Output()
+func (lf *LogFile) PostRotateCommand(filename string) {
+	if len(lf.config.Loggers.LogFile.PostRotateCommand) > 0 {
+		lf.LogInfo("execute postrotate command: %s", filename)
+		_, err := exec.Command(lf.config.Loggers.LogFile.PostRotateCommand, filename).Output()
 		if err != nil {
-			l.LogError("postrotate command error: %s", err)
-		} else if l.config.Loggers.LogFile.PostRotateDelete {
+			lf.LogError("postrotate command error: %s", err)
+		} else if lf.config.Loggers.LogFile.PostRotateDelete {
 			os.Remove(filename)
 		}
 	}
 }
 
-func (l *LogFile) CompressPostRotateCommand(filename string) {
-	if len(l.config.Loggers.LogFile.CompressPostCommand) > 0 {
+func (lf *LogFile) CompressPostRotateCommand(filename string) {
+	if len(lf.config.Loggers.LogFile.CompressPostCommand) > 0 {
 
-		l.LogInfo("execute compress postrotate command: %s", filename)
-		_, err := exec.Command(l.config.Loggers.LogFile.CompressPostCommand, filename).Output()
+		lf.LogInfo("execute compress postrotate command: %s", filename)
+		_, err := exec.Command(lf.config.Loggers.LogFile.CompressPostCommand, filename).Output()
 		if err != nil {
-			l.LogError("compress - postcommand error: %s", err)
+			lf.LogError("compress - postcommand error: %s", err)
 		}
 	}
 }
 
-func (l *LogFile) FlushWriters() {
-	switch l.config.Loggers.LogFile.Mode {
+func (lf *LogFile) FlushWriters() {
+	switch lf.config.Loggers.LogFile.Mode {
 	case pkgconfig.ModeText, pkgconfig.ModeJSON, pkgconfig.ModeFlatJSON:
-		l.writerPlain.Flush()
+		lf.writerPlain.Flush()
 	case pkgconfig.ModeDNSTap:
-		l.writerDnstap.Flush()
+		lf.writerDnstap.Flush()
 	}
 }
 
-func (l *LogFile) RotateFile() error {
+func (lf *LogFile) RotateFile() error {
 	// close writer and existing file
-	l.FlushWriters()
+	lf.FlushWriters()
 
-	if l.config.Loggers.LogFile.Mode == pkgconfig.ModeDNSTap {
-		l.writerDnstap.Close()
+	if lf.config.Loggers.LogFile.Mode == pkgconfig.ModeDNSTap {
+		lf.writerDnstap.Close()
 	}
 
-	if err := l.fileFd.Close(); err != nil {
+	if err := lf.fileFd.Close(); err != nil {
 		return err
 	}
 
 	// Rename current log file
-	bfpath := filepath.Join(l.fileDir, fmt.Sprintf("%s-%d%s", l.filePrefix, time.Now().UnixNano(), l.fileExt))
-	err := os.Rename(l.config.Loggers.LogFile.FilePath, bfpath)
+	bfpath := filepath.Join(lf.fileDir, fmt.Sprintf("%s-%d%s", lf.filePrefix, time.Now().UnixNano(), lf.fileExt))
+	err := os.Rename(lf.config.Loggers.LogFile.FilePath, bfpath)
 	if err != nil {
 		return err
 	}
 
 	// post rotate command?
-	l.PostRotateCommand(bfpath)
+	lf.PostRotateCommand(bfpath)
 
 	// keep only max files
-	err = l.Cleanup()
+	err = lf.Cleanup()
 	if err != nil {
-		l.LogError("unable to cleanup log files: %s", err)
+		lf.LogError("unable to cleanup log files: %s", err)
 		return err
 	}
 
 	// re-create new one
-	if err := l.OpenFile(); err != nil {
-		l.LogError("unable to re-create file: %s", err)
+	if err := lf.OpenFile(); err != nil {
+		lf.LogError("unable to re-create file: %s", err)
 		return err
 	}
 
 	return nil
 }
 
-func (l *LogFile) WriteToPcap(dm dnsutils.DNSMessage, pkt []gopacket.SerializableLayer) {
+func (lf *LogFile) WriteToPcap(dm dnsutils.DNSMessage, pkt []gopacket.SerializableLayer) {
 	// create the packet with the layers
 	buf := gopacket.NewSerializeBuffer()
 	opts := gopacket.SerializeOptions{
 		FixLengths:       true,
 		ComputeChecksums: true,
 	}
-	for _, l := range pkt {
-		l.SerializeTo(buf, opts)
+	for _, layer := range pkt {
+		layer.SerializeTo(buf, opts)
 	}
 
 	// rotate pcap file ?
 	bufSize := len(buf.Bytes())
 
-	if (l.fileSize + int64(bufSize)) > l.GetMaxSize() {
-		if err := l.RotateFile(); err != nil {
-			l.LogError("failed to rotate file: %s", err)
+	if (lf.fileSize + int64(bufSize)) > lf.GetMaxSize() {
+		if err := lf.RotateFile(); err != nil {
+			lf.LogError("failed to rotate file: %s", err)
 			return
 		}
 	}
@@ -411,198 +425,206 @@ func (l *LogFile) WriteToPcap(dm dnsutils.DNSMessage, pkt []gopacket.Serializabl
 		Length:        bufSize,
 	}
 
-	l.writerPcap.WritePacket(ci, buf.Bytes())
+	lf.writerPcap.WritePacket(ci, buf.Bytes())
 
 	// increase size file
-	l.fileSize += int64(bufSize)
+	lf.fileSize += int64(bufSize)
 }
 
-func (l *LogFile) WriteToPlain(data []byte) {
+func (lf *LogFile) WriteToPlain(data []byte) {
 	dataSize := int64(len(data))
 
 	// rotate file ?
-	if (l.fileSize + dataSize) > l.GetMaxSize() {
-		if err := l.RotateFile(); err != nil {
-			l.LogError("failed to rotate file: %s", err)
+	if (lf.fileSize + dataSize) > lf.GetMaxSize() {
+		if err := lf.RotateFile(); err != nil {
+			lf.LogError("failed to rotate file: %s", err)
 			return
 		}
 	}
 
 	// write log to file
-	n, _ := l.writerPlain.Write(data)
+	n, _ := lf.writerPlain.Write(data)
 
 	// increase size file
-	l.fileSize += int64(n)
+	lf.fileSize += int64(n)
 }
 
-func (l *LogFile) WriteToDnstap(data []byte) {
+func (lf *LogFile) WriteToDnstap(data []byte) {
 	dataSize := int64(len(data))
 
 	// rotate file ?
-	if (l.fileSize + dataSize) > l.GetMaxSize() {
-		if err := l.RotateFile(); err != nil {
-			l.LogError("failed to rotate file: %s", err)
+	if (lf.fileSize + dataSize) > lf.GetMaxSize() {
+		if err := lf.RotateFile(); err != nil {
+			lf.LogError("failed to rotate file: %s", err)
 			return
 		}
 	}
 
 	// write log to file
-	n, _ := l.writerDnstap.Write(data)
+	n, _ := lf.writerDnstap.Write(data)
 
 	// increase size file
-	l.fileSize += int64(n)
+	lf.fileSize += int64(n)
 }
 
-func (l *LogFile) Run() {
-	l.LogInfo("running in background...")
+func (lf *LogFile) Run() {
+	lf.LogInfo("running in background...")
+
+	// prepare next channels
+	defaultRoutes, defaultNames := lf.RoutingHandler.GetDefaultRoutes()
+	droppedRoutes, droppedNames := lf.RoutingHandler.GetDroppedRoutes()
 
 	// prepare transforms
 	listChannel := []chan dnsutils.DNSMessage{}
-	listChannel = append(listChannel, l.outputChan)
-	subprocessors := transformers.NewTransforms(&l.config.OutgoingTransformers, l.logger, l.name, listChannel, 0)
+	listChannel = append(listChannel, lf.outputChan)
+	subprocessors := transformers.NewTransforms(&lf.config.OutgoingTransformers, lf.logger, lf.name, listChannel, 0)
 
 	// goroutine to process transformed dns messages
-	go l.Process()
+	go lf.Process()
 
 	// loop to process incoming messages
 RUN_LOOP:
 	for {
 		select {
-		case <-l.stopRun:
+		case <-lf.stopRun:
 			// cleanup transformers
 			subprocessors.Reset()
-			l.doneRun <- true
+			lf.doneRun <- true
 			break RUN_LOOP
 
-		case cfg, opened := <-l.configChan:
+		case cfg, opened := <-lf.configChan:
 			if !opened {
 				return
 			}
-			l.config = cfg
-			l.ReadConfig()
+			lf.config = cfg
+			lf.ReadConfig()
 			subprocessors.ReloadConfig(&cfg.OutgoingTransformers)
 
-		case dm, opened := <-l.inputChan:
+		case dm, opened := <-lf.inputChan:
 			if !opened {
-				l.LogInfo("input channel closed!")
+				lf.LogInfo("input channel closed!")
 				return
 			}
 
 			// apply tranforms, init dns message with additionnals parts if necessary
 			subprocessors.InitDNSMessageFormat(&dm)
 			if subprocessors.ProcessMessage(&dm) == transformers.ReturnDrop {
+				lf.RoutingHandler.SendTo(droppedRoutes, droppedNames, dm)
 				continue
 			}
 
+			// send to next ?
+			lf.RoutingHandler.SendTo(defaultRoutes, defaultNames, dm)
+
 			// send to output channel
-			l.outputChan <- dm
+			lf.outputChan <- dm
 		}
 	}
-	l.LogInfo("run terminated")
+	lf.LogInfo("run terminated")
 }
 
-func (l *LogFile) Process() {
+func (lf *LogFile) Process() {
 	// prepare some timers
-	flushInterval := time.Duration(l.config.Loggers.LogFile.FlushInterval) * time.Second
+	flushInterval := time.Duration(lf.config.Loggers.LogFile.FlushInterval) * time.Second
 	flushTimer := time.NewTimer(flushInterval)
-	l.commpressTimer = time.NewTimer(time.Duration(l.config.Loggers.LogFile.CompressInterval) * time.Second)
+	lf.commpressTimer = time.NewTimer(time.Duration(lf.config.Loggers.LogFile.CompressInterval) * time.Second)
 
 	buffer := new(bytes.Buffer)
 	var data []byte
 	var err error
 
-	l.LogInfo("ready to process")
+	lf.LogInfo("ready to process")
 PROCESS_LOOP:
 	for {
 		select {
-		case <-l.stopProcess:
+		case <-lf.stopProcess:
 			// stop timer
 			flushTimer.Stop()
-			l.commpressTimer.Stop()
+			lf.commpressTimer.Stop()
 
 			// flush writer
-			l.FlushWriters()
+			lf.FlushWriters()
 
 			// closing file
-			l.LogInfo("closing log file")
-			if l.config.Loggers.LogFile.Mode == pkgconfig.ModeDNSTap {
-				l.writerDnstap.Close()
+			lf.LogInfo("closing log file")
+			if lf.config.Loggers.LogFile.Mode == pkgconfig.ModeDNSTap {
+				lf.writerDnstap.Close()
 			}
-			l.fileFd.Close()
+			lf.fileFd.Close()
 
-			l.doneProcess <- true
+			lf.doneProcess <- true
 			break PROCESS_LOOP
 
-		case dm, opened := <-l.outputChan:
+		case dm, opened := <-lf.outputChan:
 			if !opened {
-				l.LogInfo("output channel closed!")
+				lf.LogInfo("output channel closed!")
 				return
 			}
 
 			// write to file
-			switch l.config.Loggers.LogFile.Mode {
+			switch lf.config.Loggers.LogFile.Mode {
 
 			// with basic text mode
 			case pkgconfig.ModeText:
-				l.WriteToPlain(dm.Bytes(l.textFormat,
-					l.config.Global.TextFormatDelimiter,
-					l.config.Global.TextFormatBoundary))
+				lf.WriteToPlain(dm.Bytes(lf.textFormat,
+					lf.config.Global.TextFormatDelimiter,
+					lf.config.Global.TextFormatBoundary))
 
 				var delimiter bytes.Buffer
 				delimiter.WriteString("\n")
-				l.WriteToPlain(delimiter.Bytes())
+				lf.WriteToPlain(delimiter.Bytes())
 
 			// with json mode
 			case pkgconfig.ModeFlatJSON:
 				flat, err := dm.Flatten()
 				if err != nil {
-					l.LogError("flattening DNS message failed: %e", err)
+					lf.LogError("flattening DNS message failed: %e", err)
 				}
 				json.NewEncoder(buffer).Encode(flat)
-				l.WriteToPlain(buffer.Bytes())
+				lf.WriteToPlain(buffer.Bytes())
 				buffer.Reset()
 
 			// with json mode
 			case pkgconfig.ModeJSON:
 				json.NewEncoder(buffer).Encode(dm)
-				l.WriteToPlain(buffer.Bytes())
+				lf.WriteToPlain(buffer.Bytes())
 				buffer.Reset()
 
 			// with dnstap mode
 			case pkgconfig.ModeDNSTap:
 				data, err = dm.ToDNSTap()
 				if err != nil {
-					l.LogError("failed to encode to DNStap protobuf: %s", err)
+					lf.LogError("failed to encode to DNStap protobuf: %s", err)
 					continue
 				}
-				l.WriteToDnstap(data)
+				lf.WriteToDnstap(data)
 
 			// with pcap mode
 			case pkgconfig.ModePCAP:
 				pkt, err := dm.ToPacketLayer()
 				if err != nil {
-					l.LogError("failed to encode to packet layer: %s", err)
+					lf.LogError("failed to encode to packet layer: %s", err)
 					continue
 				}
 
 				// write the packet
-				l.WriteToPcap(dm, pkt)
+				lf.WriteToPcap(dm, pkt)
 			}
 
 		case <-flushTimer.C:
 			// flush writer
-			l.FlushWriters()
+			lf.FlushWriters()
 
 			// reset flush timer and buffer
 			buffer.Reset()
 			flushTimer.Reset(flushInterval)
 
-		case <-l.commpressTimer.C:
-			if l.config.Loggers.LogFile.Compress {
-				l.CompressFile()
+		case <-lf.commpressTimer.C:
+			if lf.config.Loggers.LogFile.Compress {
+				lf.CompressFile()
 			}
 
 		}
 	}
-	l.LogInfo("processing terminated")
+	lf.LogInfo("processing terminated")
 }
diff --git a/loggers/logfile_test.go b/loggers/logfile_test.go
index 6522f4c7..e1bb9660 100644
--- a/loggers/logfile_test.go
+++ b/loggers/logfile_test.go
@@ -60,7 +60,7 @@ func Test_LogFileText(t *testing.T) {
 			// send fake dns message to logger
 			dm := dnsutils.GetFakeDNSMessage()
 			dm.DNSTap.Identity = dnsutils.DNSTapIdentityTest
-			g.Channel() <- dm
+			g.GetInputChannel() <- dm
 
 			time.Sleep(time.Second)
 			g.Stop()
diff --git a/loggers/lokiclient.go b/loggers/lokiclient.go
index 57fbac03..b4815fcd 100644
--- a/loggers/lokiclient.go
+++ b/loggers/lokiclient.go
@@ -15,6 +15,7 @@ import (
 
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/transformers"
 	"github.com/dmachard/go-logger"
 	"github.com/gogo/protobuf/proto"
@@ -73,46 +74,55 @@ func (o *LokiStream) Encode2Proto() ([]byte, error) {
 }
 
 type LokiClient struct {
-	stopProcess chan bool
-	doneProcess chan bool
-	stopRun     chan bool
-	doneRun     chan bool
-	inputChan   chan dnsutils.DNSMessage
-	outputChan  chan dnsutils.DNSMessage
-	config      *pkgconfig.Config
-	configChan  chan *pkgconfig.Config
-	logger      *logger.Logger
-	httpclient  *http.Client
-	textFormat  []string
-	streams     map[string]*LokiStream
-	name        string
+	stopProcess    chan bool
+	doneProcess    chan bool
+	stopRun        chan bool
+	doneRun        chan bool
+	inputChan      chan dnsutils.DNSMessage
+	outputChan     chan dnsutils.DNSMessage
+	config         *pkgconfig.Config
+	configChan     chan *pkgconfig.Config
+	logger         *logger.Logger
+	httpclient     *http.Client
+	textFormat     []string
+	streams        map[string]*LokiStream
+	name           string
+	RoutingHandler pkgutils.RoutingHandler
 }
 
 func NewLokiClient(config *pkgconfig.Config, logger *logger.Logger, name string) *LokiClient {
 	logger.Info("[%s] logger=loki - enabled", name)
 
 	s := &LokiClient{
-		stopProcess: make(chan bool),
-		doneProcess: make(chan bool),
-		stopRun:     make(chan bool),
-		doneRun:     make(chan bool),
-		inputChan:   make(chan dnsutils.DNSMessage, config.Loggers.LokiClient.ChannelBufferSize),
-		outputChan:  make(chan dnsutils.DNSMessage, config.Loggers.LokiClient.ChannelBufferSize),
-		logger:      logger,
-		config:      config,
-		configChan:  make(chan *pkgconfig.Config),
-		streams:     make(map[string]*LokiStream),
-		name:        name,
+		stopProcess:    make(chan bool),
+		doneProcess:    make(chan bool),
+		stopRun:        make(chan bool),
+		doneRun:        make(chan bool),
+		inputChan:      make(chan dnsutils.DNSMessage, config.Loggers.LokiClient.ChannelBufferSize),
+		outputChan:     make(chan dnsutils.DNSMessage, config.Loggers.LokiClient.ChannelBufferSize),
+		logger:         logger,
+		config:         config,
+		configChan:     make(chan *pkgconfig.Config),
+		streams:        make(map[string]*LokiStream),
+		name:           name,
+		RoutingHandler: pkgutils.NewRoutingHandler(config, logger, name),
 	}
 
 	s.ReadConfig()
-
 	return s
 }
 
 func (c *LokiClient) GetName() string { return c.name }
 
-func (c *LokiClient) SetLoggers(loggers []dnsutils.Worker) {}
+func (c *LokiClient) AddDroppedRoute(wrk pkgutils.Worker) {
+	c.RoutingHandler.AddDroppedRoute(wrk)
+}
+
+func (c *LokiClient) AddDefaultRoute(wrk pkgutils.Worker) {
+	c.RoutingHandler.AddDefaultRoute(wrk)
+}
+
+func (c *LokiClient) SetLoggers(loggers []pkgutils.Worker) {}
 
 func (c *LokiClient) ReadConfig() {
 	if len(c.config.Loggers.LokiClient.TextFormat) > 0 {
@@ -176,11 +186,14 @@ func (c *LokiClient) LogError(msg string, v ...interface{}) {
 	c.logger.Error("["+c.name+"] logger=loki - "+msg, v...)
 }
 
-func (c *LokiClient) Channel() chan dnsutils.DNSMessage {
+func (c *LokiClient) GetInputChannel() chan dnsutils.DNSMessage {
 	return c.inputChan
 }
 
 func (c *LokiClient) Stop() {
+	c.LogInfo("stopping routing handler...")
+	c.RoutingHandler.Stop()
+
 	c.LogInfo("stopping to run...")
 	c.stopRun <- true
 	<-c.doneRun
@@ -193,6 +206,10 @@ func (c *LokiClient) Stop() {
 func (c *LokiClient) Run() {
 	c.LogInfo("running in background...")
 
+	// prepare next channels
+	defaultRoutes, defaultNames := c.RoutingHandler.GetDefaultRoutes()
+	droppedRoutes, droppedNames := c.RoutingHandler.GetDroppedRoutes()
+
 	// prepare transforms
 	listChannel := []chan dnsutils.DNSMessage{}
 	listChannel = append(listChannel, c.outputChan)
@@ -229,9 +246,13 @@ RUN_LOOP:
 			// apply tranforms, init dns message with additionnals parts if necessary
 			subprocessors.InitDNSMessageFormat(&dm)
 			if subprocessors.ProcessMessage(&dm) == transformers.ReturnDrop {
+				c.RoutingHandler.SendTo(droppedRoutes, droppedNames, dm)
 				continue
 			}
 
+			// send to next ?
+			c.RoutingHandler.SendTo(defaultRoutes, defaultNames, dm)
+
 			// send to output channel
 			c.outputChan <- dm
 		}
diff --git a/loggers/lokiclient_test.go b/loggers/lokiclient_test.go
index b54fb8b7..0203e809 100644
--- a/loggers/lokiclient_test.go
+++ b/loggers/lokiclient_test.go
@@ -57,7 +57,7 @@ func Test_LokiClientRun(t *testing.T) {
 			// send fake dns message to logger
 			dm := dnsutils.GetFakeDNSMessage()
 			dm.DNSTap.Identity = dnsutils.DNSTapIdentityTest
-			g.Channel() <- dm
+			g.GetInputChannel() <- dm
 
 			// accept conn
 			conn, err := fakeRcvr.Accept()
@@ -160,7 +160,7 @@ func Test_LokiClientRelabel(t *testing.T) {
 				// send fake dns message to logger
 				dm := dnsutils.GetFakeDNSMessage()
 				dm.DNSTap.Identity = dnsutils.DNSTapIdentityTest
-				g.Channel() <- dm
+				g.GetInputChannel() <- dm
 
 				// accept conn
 				conn, err := fakeRcvr.Accept()
diff --git a/loggers/prometheus.go b/loggers/prometheus.go
index f4af353d..db5ea579 100644
--- a/loggers/prometheus.go
+++ b/loggers/prometheus.go
@@ -16,6 +16,7 @@ import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/netlib"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/transformers"
 	"github.com/dmachard/go-logger"
 	"github.com/dmachard/go-topmap"
@@ -241,7 +242,8 @@ type Prometheus struct {
 	histogramQnamesLength  *prometheus.HistogramVec
 	histogramLatencies     *prometheus.HistogramVec
 
-	name string
+	name           string
+	RoutingHandler pkgutils.RoutingHandler
 }
 
 func newPrometheusCounterSet(p *Prometheus, labels prometheus.Labels) *PrometheusCountersSet {
@@ -759,18 +761,19 @@ func CreateSystemCatalogue(o *Prometheus) ([]string, *PromCounterCatalogueContai
 func NewPrometheus(config *pkgconfig.Config, logger *logger.Logger, name string) *Prometheus {
 	logger.Info("[%s] logger=prometheus - enabled", name)
 	o := &Prometheus{
-		doneAPI:      make(chan bool),
-		stopProcess:  make(chan bool),
-		doneProcess:  make(chan bool),
-		stopRun:      make(chan bool),
-		doneRun:      make(chan bool),
-		config:       config,
-		configChan:   make(chan *pkgconfig.Config),
-		inputChan:    make(chan dnsutils.DNSMessage, config.Loggers.Prometheus.ChannelBufferSize),
-		outputChan:   make(chan dnsutils.DNSMessage, config.Loggers.Prometheus.ChannelBufferSize),
-		logger:       logger,
-		promRegistry: prometheus.NewPedanticRegistry(),
-		name:         name,
+		doneAPI:        make(chan bool),
+		stopProcess:    make(chan bool),
+		doneProcess:    make(chan bool),
+		stopRun:        make(chan bool),
+		doneRun:        make(chan bool),
+		config:         config,
+		configChan:     make(chan *pkgconfig.Config),
+		inputChan:      make(chan dnsutils.DNSMessage, config.Loggers.Prometheus.ChannelBufferSize),
+		outputChan:     make(chan dnsutils.DNSMessage, config.Loggers.Prometheus.ChannelBufferSize),
+		logger:         logger,
+		promRegistry:   prometheus.NewPedanticRegistry(),
+		name:           name,
+		RoutingHandler: pkgutils.NewRoutingHandler(config, logger, name),
 	}
 
 	// This will create a catalogue of counters indexed by fileds requested by config
@@ -812,7 +815,15 @@ func NewPrometheus(config *pkgconfig.Config, logger *logger.Logger, name string)
 
 func (c *Prometheus) GetName() string { return c.name }
 
-func (c *Prometheus) SetLoggers(loggers []dnsutils.Worker) {}
+func (c *Prometheus) AddDroppedRoute(wrk pkgutils.Worker) {
+	c.RoutingHandler.AddDroppedRoute(wrk)
+}
+
+func (c *Prometheus) AddDefaultRoute(wrk pkgutils.Worker) {
+	c.RoutingHandler.AddDefaultRoute(wrk)
+}
+
+func (c *Prometheus) SetLoggers(loggers []pkgutils.Worker) {}
 
 func (c *Prometheus) InitProm() {
 
@@ -1113,11 +1124,14 @@ func (c *Prometheus) LogError(msg string, v ...interface{}) {
 	c.logger.Error("["+c.name+"] logger=prometheus - "+msg, v...)
 }
 
-func (c *Prometheus) Channel() chan dnsutils.DNSMessage {
+func (c *Prometheus) GetInputChannel() chan dnsutils.DNSMessage {
 	return c.inputChan
 }
 
 func (c *Prometheus) Stop() {
+	c.LogInfo("stopping routing handler...")
+	c.RoutingHandler.Stop()
+
 	c.LogInfo("stopping to run...")
 	c.stopRun <- true
 	<-c.doneRun
@@ -1219,6 +1233,10 @@ func (c *Prometheus) ListenAndServe() {
 func (c *Prometheus) Run() {
 	c.LogInfo("running in background...")
 
+	// prepare next channels
+	defaultRoutes, defaultNames := c.RoutingHandler.GetDefaultRoutes()
+	droppedRoutes, droppedNames := c.RoutingHandler.GetDroppedRoutes()
+
 	// prepare transforms
 	listChannel := []chan dnsutils.DNSMessage{}
 	listChannel = append(listChannel, c.outputChan)
@@ -1257,9 +1275,13 @@ RUN_LOOP:
 			// apply tranforms, init dns message with additionnals parts if necessary
 			subprocessors.InitDNSMessageFormat(&dm)
 			if subprocessors.ProcessMessage(&dm) == transformers.ReturnDrop {
+				c.RoutingHandler.SendTo(droppedRoutes, droppedNames, dm)
 				continue
 			}
 
+			// send to next ?
+			c.RoutingHandler.SendTo(defaultRoutes, defaultNames, dm)
+
 			// send to output channel
 			c.outputChan <- dm
 		}
diff --git a/loggers/redispub.go b/loggers/redispub.go
index 37e50885..cbb90c6e 100644
--- a/loggers/redispub.go
+++ b/loggers/redispub.go
@@ -15,6 +15,7 @@ import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/netlib"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/transformers"
 	"github.com/dmachard/go-logger"
 )
@@ -39,6 +40,7 @@ type RedisPub struct {
 	transportReady     chan bool
 	transportReconnect chan bool
 	writerReady        bool
+	RoutingHandler     pkgutils.RoutingHandler
 }
 
 func NewRedisPub(config *pkgconfig.Config, logger *logger.Logger, name string) *RedisPub {
@@ -58,6 +60,7 @@ func NewRedisPub(config *pkgconfig.Config, logger *logger.Logger, name string) *
 		config:             config,
 		configChan:         make(chan *pkgconfig.Config),
 		name:               name,
+		RoutingHandler:     pkgutils.NewRoutingHandler(config, logger, name),
 	}
 
 	s.ReadConfig()
@@ -67,7 +70,15 @@ func NewRedisPub(config *pkgconfig.Config, logger *logger.Logger, name string) *
 
 func (c *RedisPub) GetName() string { return c.name }
 
-func (c *RedisPub) SetLoggers(loggers []dnsutils.Worker) {}
+func (c *RedisPub) AddDroppedRoute(wrk pkgutils.Worker) {
+	c.RoutingHandler.AddDroppedRoute(wrk)
+}
+
+func (c *RedisPub) AddDefaultRoute(wrk pkgutils.Worker) {
+	c.RoutingHandler.AddDefaultRoute(wrk)
+}
+
+func (c *RedisPub) SetLoggers(loggers []pkgutils.Worker) {}
 
 func (c *RedisPub) ReadConfig() {
 
@@ -102,11 +113,14 @@ func (c *RedisPub) LogError(msg string, v ...interface{}) {
 	c.logger.Error("["+c.name+"] logger=redispub - "+msg, v...)
 }
 
-func (c *RedisPub) Channel() chan dnsutils.DNSMessage {
+func (c *RedisPub) GetInputChannel() chan dnsutils.DNSMessage {
 	return c.inputChan
 }
 
 func (c *RedisPub) Stop() {
+	c.LogInfo("stopping routing handler...")
+	c.RoutingHandler.Stop()
+
 	c.LogInfo("stopping to run...")
 	c.stopRun <- true
 	<-c.doneRun
@@ -269,6 +283,10 @@ func (c *RedisPub) FlushBuffer(buf *[]dnsutils.DNSMessage) {
 func (c *RedisPub) Run() {
 	c.LogInfo("running in background...")
 
+	// prepare next channels
+	defaultRoutes, defaultNames := c.RoutingHandler.GetDefaultRoutes()
+	droppedRoutes, droppedNames := c.RoutingHandler.GetDroppedRoutes()
+
 	// prepare transforms
 	listChannel := []chan dnsutils.DNSMessage{}
 	listChannel = append(listChannel, c.outputChan)
@@ -305,9 +323,13 @@ RUN_LOOP:
 			// apply tranforms, init dns message with additionnals parts if necessary
 			subprocessors.InitDNSMessageFormat(&dm)
 			if subprocessors.ProcessMessage(&dm) == transformers.ReturnDrop {
+				c.RoutingHandler.SendTo(droppedRoutes, droppedNames, dm)
 				continue
 			}
 
+			// send to next ?
+			c.RoutingHandler.SendTo(defaultRoutes, defaultNames, dm)
+
 			// send to output channel
 			c.outputChan <- dm
 		}
diff --git a/loggers/redispub_test.go b/loggers/redispub_test.go
index 6270b5d0..f832a524 100644
--- a/loggers/redispub_test.go
+++ b/loggers/redispub_test.go
@@ -64,7 +64,7 @@ func Test_RedisPubRun(t *testing.T) {
 
 			// send fake dns message to logger
 			dm := dnsutils.GetFakeDNSMessage()
-			g.Channel() <- dm
+			g.GetInputChannel() <- dm
 
 			// read data on server side and decode-it
 			reader := bufio.NewReader(conn)
diff --git a/loggers/restapi.go b/loggers/restapi.go
index b8c5df4e..d0db230f 100644
--- a/loggers/restapi.go
+++ b/loggers/restapi.go
@@ -11,6 +11,7 @@ import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/netlib"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/transformers"
 	"github.com/dmachard/go-logger"
 	"github.com/dmachard/go-topmap"
@@ -45,19 +46,20 @@ type KeyHit struct {
 }
 
 type RestAPI struct {
-	doneAPI     chan bool
-	stopProcess chan bool
-	doneProcess chan bool
-	stopRun     chan bool
-	doneRun     chan bool
-	inputChan   chan dnsutils.DNSMessage
-	outputChan  chan dnsutils.DNSMessage
-	httpserver  net.Listener
-	httpmux     *http.ServeMux
-	config      *pkgconfig.Config
-	configChan  chan *pkgconfig.Config
-	logger      *logger.Logger
-	name        string
+	doneAPI        chan bool
+	stopProcess    chan bool
+	doneProcess    chan bool
+	stopRun        chan bool
+	doneRun        chan bool
+	inputChan      chan dnsutils.DNSMessage
+	outputChan     chan dnsutils.DNSMessage
+	httpserver     net.Listener
+	httpmux        *http.ServeMux
+	config         *pkgconfig.Config
+	configChan     chan *pkgconfig.Config
+	logger         *logger.Logger
+	name           string
+	RoutingHandler pkgutils.RoutingHandler
 
 	HitsStream HitsStream
 	HitsUniq   HitsUniq
@@ -76,17 +78,18 @@ type RestAPI struct {
 func NewRestAPI(config *pkgconfig.Config, logger *logger.Logger, name string) *RestAPI {
 	logger.Info("[%s] logger=restapi - enabled", name)
 	o := &RestAPI{
-		doneAPI:     make(chan bool),
-		stopProcess: make(chan bool),
-		doneProcess: make(chan bool),
-		stopRun:     make(chan bool),
-		doneRun:     make(chan bool),
-		config:      config,
-		configChan:  make(chan *pkgconfig.Config),
-		inputChan:   make(chan dnsutils.DNSMessage, config.Loggers.RestAPI.ChannelBufferSize),
-		outputChan:  make(chan dnsutils.DNSMessage, config.Loggers.RestAPI.ChannelBufferSize),
-		logger:      logger,
-		name:        name,
+		doneAPI:        make(chan bool),
+		stopProcess:    make(chan bool),
+		doneProcess:    make(chan bool),
+		stopRun:        make(chan bool),
+		doneRun:        make(chan bool),
+		config:         config,
+		configChan:     make(chan *pkgconfig.Config),
+		inputChan:      make(chan dnsutils.DNSMessage, config.Loggers.RestAPI.ChannelBufferSize),
+		outputChan:     make(chan dnsutils.DNSMessage, config.Loggers.RestAPI.ChannelBufferSize),
+		logger:         logger,
+		name:           name,
+		RoutingHandler: pkgutils.NewRoutingHandler(config, logger, name),
 
 		HitsStream: HitsStream{
 			Streams: make(map[string]SearchBy),
@@ -113,7 +116,15 @@ func NewRestAPI(config *pkgconfig.Config, logger *logger.Logger, name string) *R
 
 func (c *RestAPI) GetName() string { return c.name }
 
-func (c *RestAPI) SetLoggers(loggers []dnsutils.Worker) {}
+func (c *RestAPI) AddDroppedRoute(wrk pkgutils.Worker) {
+	c.RoutingHandler.AddDroppedRoute(wrk)
+}
+
+func (c *RestAPI) AddDefaultRoute(wrk pkgutils.Worker) {
+	c.RoutingHandler.AddDefaultRoute(wrk)
+}
+
+func (c *RestAPI) SetLoggers(loggers []pkgutils.Worker) {}
 
 func (c *RestAPI) ReadConfig() {
 	if !pkgconfig.IsValidTLS(c.config.Loggers.RestAPI.TLSMinVersion) {
@@ -134,11 +145,14 @@ func (c *RestAPI) LogError(msg string, v ...interface{}) {
 	c.logger.Error("["+c.name+"] logger=restapi - "+msg, v...)
 }
 
-func (c *RestAPI) Channel() chan dnsutils.DNSMessage {
+func (c *RestAPI) GetInputChannel() chan dnsutils.DNSMessage {
 	return c.inputChan
 }
 
 func (c *RestAPI) Stop() {
+	c.LogInfo("stopping routing handler...")
+	c.RoutingHandler.Stop()
+
 	c.LogInfo("stopping to run...")
 	c.stopRun <- true
 	<-c.doneRun
@@ -697,6 +711,10 @@ func (c *RestAPI) ListenAndServe() {
 }
 
 func (c *RestAPI) Run() {
+	// prepare next channels
+	defaultRoutes, defaultNames := c.RoutingHandler.GetDefaultRoutes()
+	droppedRoutes, droppedNames := c.RoutingHandler.GetDroppedRoutes()
+
 	// prepare transforms
 	listChannel := []chan dnsutils.DNSMessage{}
 	listChannel = append(listChannel, c.outputChan)
@@ -736,9 +754,13 @@ RUN_LOOP:
 			// apply tranforms, init dns message with additionnals parts if necessary
 			subprocessors.InitDNSMessageFormat(&dm)
 			if subprocessors.ProcessMessage(&dm) == transformers.ReturnDrop {
+				c.RoutingHandler.SendTo(droppedRoutes, droppedNames, dm)
 				continue
 			}
 
+			// send to next ?
+			c.RoutingHandler.SendTo(defaultRoutes, defaultNames, dm)
+
 			// send to output channel
 			c.outputChan <- dm
 		}
diff --git a/loggers/scalyr.go b/loggers/scalyr.go
index 4e495392..daf63efc 100644
--- a/loggers/scalyr.go
+++ b/loggers/scalyr.go
@@ -18,6 +18,7 @@ import (
 
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/transformers"
 	"github.com/dmachard/go-logger"
 )
@@ -25,18 +26,20 @@ import (
 // ScalyrClient is a client for Scalyr(https://www.dataset.com/)
 // This client is using the addEvents endpoint, described here: https://app.scalyr.com/help/api#addEvents
 type ScalyrClient struct {
-	stopProcess chan bool
-	doneProcess chan bool
-	stopRun     chan bool
-	doneRun     chan bool
-	inputChan   chan dnsutils.DNSMessage
-	outputChan  chan dnsutils.DNSMessage
-	logger      *logger.Logger
-	name        string
-	config      *pkgconfig.Config
-	configChan  chan *pkgconfig.Config
-	mode        string
-	textFormat  []string
+	stopProcess    chan bool
+	doneProcess    chan bool
+	stopRun        chan bool
+	doneRun        chan bool
+	inputChan      chan dnsutils.DNSMessage
+	outputChan     chan dnsutils.DNSMessage
+	logger         *logger.Logger
+	name           string
+	config         *pkgconfig.Config
+	configChan     chan *pkgconfig.Config
+	RoutingHandler pkgutils.RoutingHandler
+
+	mode       string
+	textFormat []string
 
 	session string // Session ID, used by scalyr, see API docs
 
@@ -54,17 +57,19 @@ type ScalyrClient struct {
 func NewScalyrClient(config *pkgconfig.Config, console *logger.Logger, name string) *ScalyrClient {
 	console.Info("[%s] logger=scalyr - starting", name)
 	c := &ScalyrClient{
-		stopProcess: make(chan bool),
-		doneProcess: make(chan bool),
-		stopRun:     make(chan bool),
-		doneRun:     make(chan bool),
-		inputChan:   make(chan dnsutils.DNSMessage, config.Loggers.ScalyrClient.ChannelBufferSize),
-		outputChan:  make(chan dnsutils.DNSMessage, config.Loggers.ScalyrClient.ChannelBufferSize),
-		logger:      console,
-		name:        name,
-		config:      config,
-		configChan:  make(chan *pkgconfig.Config),
-		mode:        pkgconfig.ModeText,
+		stopProcess:    make(chan bool),
+		doneProcess:    make(chan bool),
+		stopRun:        make(chan bool),
+		doneRun:        make(chan bool),
+		inputChan:      make(chan dnsutils.DNSMessage, config.Loggers.ScalyrClient.ChannelBufferSize),
+		outputChan:     make(chan dnsutils.DNSMessage, config.Loggers.ScalyrClient.ChannelBufferSize),
+		logger:         console,
+		name:           name,
+		config:         config,
+		configChan:     make(chan *pkgconfig.Config),
+		RoutingHandler: pkgutils.NewRoutingHandler(config, console, name),
+
+		mode: pkgconfig.ModeText,
 
 		endpoint: makeEndpoint("app.scalyr.com"),
 		flush:    time.NewTicker(30 * time.Second),
@@ -153,6 +158,10 @@ func (c *ScalyrClient) ReloadConfig(config *pkgconfig.Config) {
 func (c *ScalyrClient) Run() {
 	c.LogInfo("running in background...")
 
+	// prepare next channels
+	defaultRoutes, defaultNames := c.RoutingHandler.GetDefaultRoutes()
+	droppedRoutes, droppedNames := c.RoutingHandler.GetDroppedRoutes()
+
 	// prepare transforms
 	listChannel := []chan dnsutils.DNSMessage{}
 	listChannel = append(listChannel, c.outputChan)
@@ -189,9 +198,13 @@ RUN_LOOP:
 			// apply tranforms, init dns message with additionnals parts if necessary
 			subprocessors.InitDNSMessageFormat(&dm)
 			if subprocessors.ProcessMessage(&dm) == transformers.ReturnDrop {
+				c.RoutingHandler.SendTo(droppedRoutes, droppedNames, dm)
 				continue
 			}
 
+			// send to next ?
+			c.RoutingHandler.SendTo(defaultRoutes, defaultNames, dm)
+
 			// send to output channel
 			c.outputChan <- dm
 		}
@@ -289,6 +302,9 @@ PROCESS_LOOP:
 }
 
 func (c ScalyrClient) Stop() {
+	c.LogInfo("stopping routing handler...")
+	c.RoutingHandler.Stop()
+
 	c.LogInfo("stopping to run...")
 	c.stopRun <- true
 	<-c.doneRun
@@ -437,8 +453,16 @@ type response struct {
 
 func (c *ScalyrClient) GetName() string { return c.name }
 
-func (c *ScalyrClient) SetLoggers(loggers []dnsutils.Worker) {}
+func (c *ScalyrClient) AddDroppedRoute(wrk pkgutils.Worker) {
+	c.RoutingHandler.AddDroppedRoute(wrk)
+}
+
+func (c *ScalyrClient) AddDefaultRoute(wrk pkgutils.Worker) {
+	c.RoutingHandler.AddDefaultRoute(wrk)
+}
+
+func (c *ScalyrClient) SetLoggers(loggers []pkgutils.Worker) {}
 
-func (c *ScalyrClient) Channel() chan dnsutils.DNSMessage {
+func (c *ScalyrClient) GetInputChannel() chan dnsutils.DNSMessage {
 	return c.inputChan
 }
diff --git a/loggers/statsd.go b/loggers/statsd.go
index 80a72b97..cc9b93c7 100644
--- a/loggers/statsd.go
+++ b/loggers/statsd.go
@@ -12,6 +12,7 @@ import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/netlib"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/transformers"
 	"github.com/dmachard/go-logger"
 	"github.com/dmachard/go-topmap"
@@ -44,16 +45,17 @@ type StreamStats struct {
 }
 
 type StatsdClient struct {
-	stopProcess chan bool
-	doneProcess chan bool
-	stopRun     chan bool
-	doneRun     chan bool
-	inputChan   chan dnsutils.DNSMessage
-	outputChan  chan dnsutils.DNSMessage
-	config      *pkgconfig.Config
-	configChan  chan *pkgconfig.Config
-	logger      *logger.Logger
-	name        string
+	stopProcess    chan bool
+	doneProcess    chan bool
+	stopRun        chan bool
+	doneRun        chan bool
+	inputChan      chan dnsutils.DNSMessage
+	outputChan     chan dnsutils.DNSMessage
+	config         *pkgconfig.Config
+	configChan     chan *pkgconfig.Config
+	logger         *logger.Logger
+	name           string
+	RoutingHandler pkgutils.RoutingHandler
 
 	Stats StreamStats
 	sync.RWMutex
@@ -63,17 +65,18 @@ func NewStatsdClient(config *pkgconfig.Config, logger *logger.Logger, name strin
 	logger.Info("[%s] logger=statsd - enabled", name)
 
 	s := &StatsdClient{
-		stopProcess: make(chan bool),
-		doneProcess: make(chan bool),
-		stopRun:     make(chan bool),
-		doneRun:     make(chan bool),
-		inputChan:   make(chan dnsutils.DNSMessage, config.Loggers.Statsd.ChannelBufferSize),
-		outputChan:  make(chan dnsutils.DNSMessage, config.Loggers.Statsd.ChannelBufferSize),
-		logger:      logger,
-		config:      config,
-		configChan:  make(chan *pkgconfig.Config),
-		name:        name,
-		Stats:       StreamStats{Streams: make(map[string]*StatsPerStream)},
+		stopProcess:    make(chan bool),
+		doneProcess:    make(chan bool),
+		stopRun:        make(chan bool),
+		doneRun:        make(chan bool),
+		inputChan:      make(chan dnsutils.DNSMessage, config.Loggers.Statsd.ChannelBufferSize),
+		outputChan:     make(chan dnsutils.DNSMessage, config.Loggers.Statsd.ChannelBufferSize),
+		logger:         logger,
+		config:         config,
+		configChan:     make(chan *pkgconfig.Config),
+		name:           name,
+		Stats:          StreamStats{Streams: make(map[string]*StatsPerStream)},
+		RoutingHandler: pkgutils.NewRoutingHandler(config, logger, name),
 	}
 
 	// check config
@@ -84,7 +87,15 @@ func NewStatsdClient(config *pkgconfig.Config, logger *logger.Logger, name strin
 
 func (c *StatsdClient) GetName() string { return c.name }
 
-func (c *StatsdClient) SetLoggers(loggers []dnsutils.Worker) {}
+func (c *StatsdClient) AddDroppedRoute(wrk pkgutils.Worker) {
+	c.RoutingHandler.AddDroppedRoute(wrk)
+}
+
+func (c *StatsdClient) AddDefaultRoute(wrk pkgutils.Worker) {
+	c.RoutingHandler.AddDefaultRoute(wrk)
+}
+
+func (c *StatsdClient) SetLoggers(loggers []pkgutils.Worker) {}
 
 func (c *StatsdClient) ReadConfig() {
 	if !pkgconfig.IsValidTLS(c.config.Loggers.Statsd.TLSMinVersion) {
@@ -105,11 +116,14 @@ func (c *StatsdClient) LogError(msg string, v ...interface{}) {
 	c.logger.Error("["+c.name+"] logger=statsd - "+msg, v...)
 }
 
-func (c *StatsdClient) Channel() chan dnsutils.DNSMessage {
+func (c *StatsdClient) GetInputChannel() chan dnsutils.DNSMessage {
 	return c.inputChan
 }
 
 func (c *StatsdClient) Stop() {
+	c.LogInfo("stopping routing handler...")
+	c.RoutingHandler.Stop()
+
 	c.LogInfo("stopping to run...")
 	c.stopRun <- true
 	<-c.doneRun
@@ -235,6 +249,10 @@ func (c *StatsdClient) RecordDNSMessage(dm dnsutils.DNSMessage) {
 func (c *StatsdClient) Run() {
 	c.LogInfo("running in background...")
 
+	// prepare next channels
+	defaultRoutes, defaultNames := c.RoutingHandler.GetDefaultRoutes()
+	droppedRoutes, droppedNames := c.RoutingHandler.GetDroppedRoutes()
+
 	// prepare transforms
 	listChannel := []chan dnsutils.DNSMessage{}
 	listChannel = append(listChannel, c.outputChan)
@@ -271,9 +289,13 @@ RUN_LOOP:
 			// apply tranforms, init dns message with additionnals parts if necessary
 			subprocessors.InitDNSMessageFormat(&dm)
 			if subprocessors.ProcessMessage(&dm) == transformers.ReturnDrop {
+				c.RoutingHandler.SendTo(droppedRoutes, droppedNames, dm)
 				continue
 			}
 
+			// send to next ?
+			c.RoutingHandler.SendTo(defaultRoutes, defaultNames, dm)
+
 			// send to output channel
 			c.outputChan <- dm
 		}
diff --git a/loggers/statsd_test.go b/loggers/statsd_test.go
index 6874c41f..91ae02d0 100644
--- a/loggers/statsd_test.go
+++ b/loggers/statsd_test.go
@@ -29,7 +29,7 @@ func TestStatsdRun(t *testing.T) {
 
 	// send fake dns message to logger
 	dm := dnsutils.GetFakeDNSMessage()
-	g.Channel() <- dm
+	g.GetInputChannel() <- dm
 
 	// read data on fake server side
 	buf := make([]byte, 4096)
diff --git a/loggers/stdout.go b/loggers/stdout.go
index 3d1a407a..e22afa3c 100644
--- a/loggers/stdout.go
+++ b/loggers/stdout.go
@@ -11,6 +11,7 @@ import (
 
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/transformers"
 	"github.com/dmachard/go-logger"
 	"github.com/google/gopacket"
@@ -31,179 +32,200 @@ func IsStdoutValidMode(mode string) bool {
 }
 
 type StdOut struct {
-	stopProcess chan bool
-	doneProcess chan bool
-	stopRun     chan bool
-	doneRun     chan bool
-	inputChan   chan dnsutils.DNSMessage
-	outputChan  chan dnsutils.DNSMessage
-	textFormat  []string
-	config      *pkgconfig.Config
-	configChan  chan *pkgconfig.Config
-	logger      *logger.Logger
-	writerText  *log.Logger
-	writerPcap  *pcapgo.Writer
-	name        string
+	stopProcess    chan bool
+	doneProcess    chan bool
+	stopRun        chan bool
+	doneRun        chan bool
+	inputChan      chan dnsutils.DNSMessage
+	outputChan     chan dnsutils.DNSMessage
+	textFormat     []string
+	config         *pkgconfig.Config
+	configChan     chan *pkgconfig.Config
+	logger         *logger.Logger
+	writerText     *log.Logger
+	writerPcap     *pcapgo.Writer
+	name           string
+	RoutingHandler pkgutils.RoutingHandler
 }
 
 func NewStdOut(config *pkgconfig.Config, console *logger.Logger, name string) *StdOut {
 	console.Info("[%s] logger=stdout - enabled", name)
-	o := &StdOut{
-		stopProcess: make(chan bool),
-		doneProcess: make(chan bool),
-		stopRun:     make(chan bool),
-		doneRun:     make(chan bool),
-		inputChan:   make(chan dnsutils.DNSMessage, config.Loggers.Stdout.ChannelBufferSize),
-		outputChan:  make(chan dnsutils.DNSMessage, config.Loggers.Stdout.ChannelBufferSize),
-		logger:      console,
-		config:      config,
-		configChan:  make(chan *pkgconfig.Config),
-		writerText:  log.New(os.Stdout, "", 0),
-		name:        name,
+	so := &StdOut{
+		stopProcess:    make(chan bool),
+		doneProcess:    make(chan bool),
+		stopRun:        make(chan bool),
+		doneRun:        make(chan bool),
+		inputChan:      make(chan dnsutils.DNSMessage, config.Loggers.Stdout.ChannelBufferSize),
+		outputChan:     make(chan dnsutils.DNSMessage, config.Loggers.Stdout.ChannelBufferSize),
+		logger:         console,
+		config:         config,
+		configChan:     make(chan *pkgconfig.Config),
+		writerText:     log.New(os.Stdout, "", 0),
+		name:           name,
+		RoutingHandler: pkgutils.NewRoutingHandler(config, console, name),
 	}
-	o.ReadConfig()
-	return o
+	so.ReadConfig()
+	return so
 }
 
-func (c *StdOut) GetName() string { return c.name }
+func (so *StdOut) GetName() string { return so.name }
 
-func (c *StdOut) SetLoggers(loggers []dnsutils.Worker) {}
+func (so *StdOut) AddDroppedRoute(wrk pkgutils.Worker) {
+	so.RoutingHandler.AddDroppedRoute(wrk)
+}
+
+func (so *StdOut) AddDefaultRoute(wrk pkgutils.Worker) {
+	so.RoutingHandler.AddDefaultRoute(wrk)
+}
+
+func (so *StdOut) SetLoggers(loggers []pkgutils.Worker) {}
 
-func (c *StdOut) ReadConfig() {
-	if !IsStdoutValidMode(c.config.Loggers.Stdout.Mode) {
-		c.logger.Fatal("["+c.name+"] logger=stdout - invalid mode: ", c.config.Loggers.Stdout.Mode)
+func (so *StdOut) ReadConfig() {
+	if !IsStdoutValidMode(so.config.Loggers.Stdout.Mode) {
+		so.logger.Fatal("["+so.name+"] logger=stdout - invalid mode: ", so.config.Loggers.Stdout.Mode)
 	}
 
-	if len(c.config.Loggers.Stdout.TextFormat) > 0 {
-		c.textFormat = strings.Fields(c.config.Loggers.Stdout.TextFormat)
+	if len(so.config.Loggers.Stdout.TextFormat) > 0 {
+		so.textFormat = strings.Fields(so.config.Loggers.Stdout.TextFormat)
 	} else {
-		c.textFormat = strings.Fields(c.config.Global.TextFormat)
+		so.textFormat = strings.Fields(so.config.Global.TextFormat)
 	}
 }
 
-func (c *StdOut) ReloadConfig(config *pkgconfig.Config) {
-	c.LogInfo("reload configuration!")
-	c.configChan <- config
+func (so *StdOut) ReloadConfig(config *pkgconfig.Config) {
+	so.LogInfo("reload configuration!")
+	so.configChan <- config
 }
 
-func (c *StdOut) LogInfo(msg string, v ...interface{}) {
-	c.logger.Info("["+c.name+"] logger=stdout - "+msg, v...)
+func (so *StdOut) LogInfo(msg string, v ...interface{}) {
+	so.logger.Info("["+so.name+"] logger=stdout - "+msg, v...)
 }
 
-func (c *StdOut) LogError(msg string, v ...interface{}) {
-	c.logger.Error("["+c.name+"] logger=stdout - "+msg, v...)
+func (so *StdOut) LogError(msg string, v ...interface{}) {
+	so.logger.Error("["+so.name+"] logger=stdout - "+msg, v...)
 }
 
-func (c *StdOut) SetTextWriter(b *bytes.Buffer) {
-	c.writerText = log.New(os.Stdout, "", 0)
-	c.writerText.SetOutput(b)
+func (so *StdOut) SetTextWriter(b *bytes.Buffer) {
+	so.writerText = log.New(os.Stdout, "", 0)
+	so.writerText.SetOutput(b)
 }
 
-func (c *StdOut) SetPcapWriter(w io.Writer) {
-	c.LogInfo("init pcap writer")
+func (so *StdOut) SetPcapWriter(w io.Writer) {
+	so.LogInfo("init pcap writer")
 
-	c.writerPcap = pcapgo.NewWriter(w)
-	if err := c.writerPcap.WriteFileHeader(65536, layers.LinkTypeEthernet); err != nil {
-		c.logger.Fatal("["+c.name+"] logger=stdout - pcap init error: %e", err)
+	so.writerPcap = pcapgo.NewWriter(w)
+	if err := so.writerPcap.WriteFileHeader(65536, layers.LinkTypeEthernet); err != nil {
+		so.logger.Fatal("["+so.name+"] logger=stdout - pcap init error: %e", err)
 	}
 }
 
-func (c *StdOut) Channel() chan dnsutils.DNSMessage {
-	return c.inputChan
+func (so *StdOut) GetInputChannel() chan dnsutils.DNSMessage {
+	return so.inputChan
 }
 
-func (c *StdOut) Stop() {
-	c.LogInfo("stopping to run...")
-	c.stopRun <- true
-	<-c.doneRun
+func (so *StdOut) Stop() {
+	so.LogInfo("stopping routing handler...")
+	so.RoutingHandler.Stop()
 
-	c.LogInfo("stopping to process...")
-	c.stopProcess <- true
-	<-c.doneProcess
+	so.LogInfo("stopping to run...")
+	so.stopRun <- true
+	<-so.doneRun
+
+	so.LogInfo("stopping to process...")
+	so.stopProcess <- true
+	<-so.doneProcess
 }
 
-func (c *StdOut) Run() {
-	c.LogInfo("running in background...")
+func (so *StdOut) Run() {
+	so.LogInfo("running in background...")
+
+	// prepare next channels
+	defaultRoutes, defaultNames := so.RoutingHandler.GetDefaultRoutes()
+	droppedRoutes, droppedNames := so.RoutingHandler.GetDroppedRoutes()
 
 	// prepare transforms
 	listChannel := []chan dnsutils.DNSMessage{}
-	listChannel = append(listChannel, c.outputChan)
-	subprocessors := transformers.NewTransforms(&c.config.OutgoingTransformers, c.logger, c.name, listChannel, 0)
+	listChannel = append(listChannel, so.outputChan)
+	subprocessors := transformers.NewTransforms(&so.config.OutgoingTransformers, so.logger, so.name, listChannel, 0)
 
 	// goroutine to process transformed dns messages
-	go c.Process()
+	go so.Process()
 
 	// loop to process incoming messages
 RUN_LOOP:
 	for {
 		select {
-		case <-c.stopRun:
+		case <-so.stopRun:
 			// cleanup transformers
 			subprocessors.Reset()
-			c.doneRun <- true
+			so.doneRun <- true
 			break RUN_LOOP
 
 		// new config provided?
-		case cfg, opened := <-c.configChan:
+		case cfg, opened := <-so.configChan:
 			if !opened {
 				return
 			}
-			c.config = cfg
-			c.ReadConfig()
+			so.config = cfg
+			so.ReadConfig()
 			subprocessors.ReloadConfig(&cfg.OutgoingTransformers)
 
-		case dm, opened := <-c.inputChan:
+		case dm, opened := <-so.inputChan:
 			if !opened {
-				c.LogInfo("run: input channel closed!")
+				so.LogInfo("run: input channel closed!")
 				return
 			}
 
 			// apply tranforms, init dns message with additionnals parts if necessary
 			subprocessors.InitDNSMessageFormat(&dm)
 			if subprocessors.ProcessMessage(&dm) == transformers.ReturnDrop {
+				so.RoutingHandler.SendTo(droppedRoutes, droppedNames, dm)
 				continue
 			}
 
+			// send to next ?
+			so.RoutingHandler.SendTo(defaultRoutes, defaultNames, dm)
+
 			// send to output channel
-			c.outputChan <- dm
+			so.outputChan <- dm
 		}
 	}
-	c.LogInfo("run terminated")
+	so.LogInfo("run terminated")
 }
 
-func (c *StdOut) Process() {
+func (so *StdOut) Process() {
 
 	// standard output buffer
 	buffer := new(bytes.Buffer)
 
-	if c.config.Loggers.Stdout.Mode == pkgconfig.ModePCAP && c.writerPcap == nil {
-		c.SetPcapWriter(os.Stdout)
+	if so.config.Loggers.Stdout.Mode == pkgconfig.ModePCAP && so.writerPcap == nil {
+		so.SetPcapWriter(os.Stdout)
 	}
 
-	c.LogInfo("ready to process")
+	so.LogInfo("ready to process")
 PROCESS_LOOP:
 	for {
 		select {
-		case <-c.stopProcess:
-			c.doneProcess <- true
+		case <-so.stopProcess:
+			so.doneProcess <- true
 			break PROCESS_LOOP
 
-		case dm, opened := <-c.outputChan:
+		case dm, opened := <-so.outputChan:
 			if !opened {
-				c.LogInfo("process: output channel closed!")
+				so.LogInfo("process: output channel closed!")
 				return
 			}
 
-			switch c.config.Loggers.Stdout.Mode {
+			switch so.config.Loggers.Stdout.Mode {
 			case pkgconfig.ModePCAP:
 				if len(dm.DNS.Payload) == 0 {
-					c.LogError("process: no dns payload to encode, drop it")
+					so.LogError("process: no dns payload to encode, drop it")
 					continue
 				}
 
 				pkt, err := dm.ToPacketLayer()
 				if err != nil {
-					c.LogError("unable to pack layer: %s", err)
+					so.LogError("unable to pack layer: %s", err)
 					continue
 				}
 
@@ -223,28 +245,28 @@ PROCESS_LOOP:
 					Length:        bufSize,
 				}
 
-				c.writerPcap.WritePacket(ci, buf.Bytes())
+				so.writerPcap.WritePacket(ci, buf.Bytes())
 
 			case pkgconfig.ModeText:
-				c.writerText.Print(dm.String(c.textFormat,
-					c.config.Global.TextFormatDelimiter,
-					c.config.Global.TextFormatBoundary))
+				so.writerText.Print(dm.String(so.textFormat,
+					so.config.Global.TextFormatDelimiter,
+					so.config.Global.TextFormatBoundary))
 
 			case pkgconfig.ModeJSON:
 				json.NewEncoder(buffer).Encode(dm)
-				c.writerText.Print(buffer.String())
+				so.writerText.Print(buffer.String())
 				buffer.Reset()
 
 			case pkgconfig.ModeFlatJSON:
 				flat, err := dm.Flatten()
 				if err != nil {
-					c.LogError("process: flattening DNS message failed: %e", err)
+					so.LogError("process: flattening DNS message failed: %e", err)
 				}
 				json.NewEncoder(buffer).Encode(flat)
-				c.writerText.Print(buffer.String())
+				so.writerText.Print(buffer.String())
 				buffer.Reset()
 			}
 		}
 	}
-	c.LogInfo("processing terminated")
+	so.LogInfo("processing terminated")
 }
diff --git a/loggers/stdout_test.go b/loggers/stdout_test.go
index fd701d22..c8c717a2 100644
--- a/loggers/stdout_test.go
+++ b/loggers/stdout_test.go
@@ -77,7 +77,7 @@ func Test_StdoutTextMode(t *testing.T) {
 			// print dns message to stdout buffer
 			dm := dnsutils.GetFakeDNSMessage()
 			dm.DNS.Qname = tc.qname
-			g.Channel() <- dm
+			g.GetInputChannel() <- dm
 
 			// stop logger
 			time.Sleep(time.Second)
@@ -120,7 +120,7 @@ func Test_StdoutJsonMode(t *testing.T) {
 
 			// print dns message to stdout buffer
 			dm := dnsutils.GetFakeDNSMessage()
-			g.Channel() <- dm
+			g.GetInputChannel() <- dm
 
 			// stop logger
 			time.Sleep(time.Second)
@@ -151,7 +151,7 @@ func Test_StdoutPcapMode(t *testing.T) {
 
 	// send DNSMessage to channel
 	dm := dnsutils.GetFakeDNSMessageWithPayload()
-	g.Channel() <- dm
+	g.GetInputChannel() <- dm
 
 	// stop logger
 	time.Sleep(time.Second)
@@ -192,7 +192,7 @@ func Test_StdoutPcapMode_NoDNSPayload(t *testing.T) {
 
 	// send DNSMessage to channel
 	dm := dnsutils.GetFakeDNSMessage()
-	g.Channel() <- dm
+	g.GetInputChannel() <- dm
 
 	// stop logger
 	time.Sleep(time.Second)
diff --git a/loggers/syslog.go b/loggers/syslog.go
index fa817058..a913fdfa 100644
--- a/loggers/syslog.go
+++ b/loggers/syslog.go
@@ -14,6 +14,7 @@ import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/netlib"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/transformers"
 	"github.com/dmachard/go-logger"
 )
@@ -72,6 +73,7 @@ type Syslog struct {
 	transportReconnect chan bool
 	textFormat         []string
 	name               string
+	RoutingHandler     pkgutils.RoutingHandler
 }
 
 func NewSyslog(config *pkgconfig.Config, console *logger.Logger, name string) *Syslog {
@@ -89,6 +91,7 @@ func NewSyslog(config *pkgconfig.Config, console *logger.Logger, name string) *S
 		config:             config,
 		configChan:         make(chan *pkgconfig.Config),
 		name:               name,
+		RoutingHandler:     pkgutils.NewRoutingHandler(config, console, name),
 	}
 	s.ReadConfig()
 	return s
@@ -96,7 +99,15 @@ func NewSyslog(config *pkgconfig.Config, console *logger.Logger, name string) *S
 
 func (s *Syslog) GetName() string { return s.name }
 
-func (s *Syslog) SetLoggers(loggers []dnsutils.Worker) {}
+func (s *Syslog) AddDroppedRoute(wrk pkgutils.Worker) {
+	s.RoutingHandler.AddDroppedRoute(wrk)
+}
+
+func (s *Syslog) AddDefaultRoute(wrk pkgutils.Worker) {
+	s.RoutingHandler.AddDefaultRoute(wrk)
+}
+
+func (s *Syslog) SetLoggers(loggers []pkgutils.Worker) {}
 
 func (s *Syslog) ReadConfig() {
 	if !pkgconfig.IsValidTLS(s.config.Loggers.Syslog.TLSMinVersion) {
@@ -130,7 +141,7 @@ func (s *Syslog) ReloadConfig(config *pkgconfig.Config) {
 	s.configChan <- config
 }
 
-func (s *Syslog) Channel() chan dnsutils.DNSMessage {
+func (s *Syslog) GetInputChannel() chan dnsutils.DNSMessage {
 	return s.inputChan
 }
 
@@ -143,6 +154,9 @@ func (s *Syslog) LogError(msg string, v ...interface{}) {
 }
 
 func (s *Syslog) Stop() {
+	s.LogInfo("stopping routing handler...")
+	s.RoutingHandler.Stop()
+
 	s.LogInfo("stopping to run...")
 	s.stopRun <- true
 	<-s.doneRun
@@ -245,6 +259,10 @@ func (s *Syslog) ConnectToRemote() {
 func (s *Syslog) Run() {
 	s.LogInfo("running in background...")
 
+	// prepare next channels
+	defaultRoutes, defaultNames := s.RoutingHandler.GetDefaultRoutes()
+	droppedRoutes, droppedNames := s.RoutingHandler.GetDroppedRoutes()
+
 	// prepare transforms
 	listChannel := []chan dnsutils.DNSMessage{}
 	listChannel = append(listChannel, s.outputChan)
@@ -285,9 +303,13 @@ RUN_LOOP:
 			// apply tranforms, init dns message with additionnals parts if necessary
 			subprocessors.InitDNSMessageFormat(&dm)
 			if subprocessors.ProcessMessage(&dm) == transformers.ReturnDrop {
+				s.RoutingHandler.SendTo(droppedRoutes, droppedNames, dm)
 				continue
 			}
 
+			// send to next ?
+			s.RoutingHandler.SendTo(defaultRoutes, defaultNames, dm)
+
 			// send to output channel
 			s.outputChan <- dm
 		}
diff --git a/loggers/syslog_test.go b/loggers/syslog_test.go
index be3363b1..9e4a2901 100644
--- a/loggers/syslog_test.go
+++ b/loggers/syslog_test.go
@@ -88,7 +88,7 @@ func Test_SyslogRunUdp(t *testing.T) {
 			// send fake dns message to logger
 			time.Sleep(time.Second)
 			dm := dnsutils.GetFakeDNSMessage()
-			g.Channel() <- dm
+			g.GetInputChannel() <- dm
 
 			// read data on fake server side
 			buf := make([]byte, 4096)
@@ -191,7 +191,7 @@ func Test_SyslogRunTcp(t *testing.T) {
 			// send fake dns message to logger
 			time.Sleep(time.Second)
 			dm := dnsutils.GetFakeDNSMessage()
-			g.Channel() <- dm
+			g.GetInputChannel() <- dm
 
 			// read data on server side and decode-it
 			reader := bufio.NewReader(conn)
@@ -235,7 +235,7 @@ func Test_SyslogRun_RemoveNullCharacter(t *testing.T) {
 	time.Sleep(time.Second)
 	dm := dnsutils.GetFakeDNSMessage()
 	dm.DNS.Qname = "null\x00char.com"
-	g.Channel() <- dm
+	g.GetInputChannel() <- dm
 
 	// read data on fake server side
 	buf := make([]byte, (500))
diff --git a/loggers/tcpclient.go b/loggers/tcpclient.go
index 12a47c70..e426830b 100644
--- a/loggers/tcpclient.go
+++ b/loggers/tcpclient.go
@@ -14,6 +14,7 @@ import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/netlib"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/transformers"
 	"github.com/dmachard/go-logger"
 )
@@ -38,6 +39,7 @@ type TCPClient struct {
 	transportReady     chan bool
 	transportReconnect chan bool
 	writerReady        bool
+	RoutingHandler     pkgutils.RoutingHandler
 }
 
 func NewTCPClient(config *pkgconfig.Config, logger *logger.Logger, name string) *TCPClient {
@@ -57,6 +59,7 @@ func NewTCPClient(config *pkgconfig.Config, logger *logger.Logger, name string)
 		config:             config,
 		configChan:         make(chan *pkgconfig.Config),
 		name:               name,
+		RoutingHandler:     pkgutils.NewRoutingHandler(config, logger, name),
 	}
 
 	s.ReadConfig()
@@ -66,7 +69,15 @@ func NewTCPClient(config *pkgconfig.Config, logger *logger.Logger, name string)
 
 func (c *TCPClient) GetName() string { return c.name }
 
-func (c *TCPClient) SetLoggers(loggers []dnsutils.Worker) {}
+func (c *TCPClient) AddDroppedRoute(wrk pkgutils.Worker) {
+	c.RoutingHandler.AddDroppedRoute(wrk)
+}
+
+func (c *TCPClient) AddDefaultRoute(wrk pkgutils.Worker) {
+	c.RoutingHandler.AddDefaultRoute(wrk)
+}
+
+func (c *TCPClient) SetLoggers(loggers []pkgutils.Worker) {}
 
 func (c *TCPClient) ReadConfig() {
 	c.transport = c.config.Loggers.TCPClient.Transport
@@ -100,11 +111,14 @@ func (c *TCPClient) LogError(msg string, v ...interface{}) {
 	c.logger.Error("["+c.name+"] logger=tcpclient - "+msg, v...)
 }
 
-func (c *TCPClient) Channel() chan dnsutils.DNSMessage {
+func (c *TCPClient) GetInputChannel() chan dnsutils.DNSMessage {
 	return c.inputChan
 }
 
 func (c *TCPClient) Stop() {
+	c.LogInfo("stopping routing handler...")
+	c.RoutingHandler.Stop()
+
 	c.LogInfo("stopping to run...")
 	c.stopRun <- true
 	<-c.doneRun
@@ -257,6 +271,10 @@ func (c *TCPClient) FlushBuffer(buf *[]dnsutils.DNSMessage) {
 func (c *TCPClient) Run() {
 	c.LogInfo("running in background...")
 
+	// prepare next channels
+	defaultRoutes, defaultNames := c.RoutingHandler.GetDefaultRoutes()
+	droppedRoutes, droppedNames := c.RoutingHandler.GetDroppedRoutes()
+
 	// prepare transforms
 	listChannel := []chan dnsutils.DNSMessage{}
 	listChannel = append(listChannel, c.outputChan)
@@ -293,9 +311,13 @@ RUN_LOOP:
 			// apply tranforms, init dns message with additionnals parts if necessary
 			subprocessors.InitDNSMessageFormat(&dm)
 			if subprocessors.ProcessMessage(&dm) == transformers.ReturnDrop {
+				c.RoutingHandler.SendTo(droppedRoutes, droppedNames, dm)
 				continue
 			}
 
+			// send to next ?
+			c.RoutingHandler.SendTo(defaultRoutes, defaultNames, dm)
+
 			// send to output channel
 			c.outputChan <- dm
 		}
diff --git a/loggers/tcpclient_test.go b/loggers/tcpclient_test.go
index 94e13164..8e2c0371 100644
--- a/loggers/tcpclient_test.go
+++ b/loggers/tcpclient_test.go
@@ -65,7 +65,7 @@ func Test_TcpClientRun(t *testing.T) {
 
 			// send fake dns message to logger
 			dm := dnsutils.GetFakeDNSMessage()
-			g.Channel() <- dm
+			g.GetInputChannel() <- dm
 
 			// read data on server side and decode-it
 			reader := bufio.NewReader(conn)
diff --git a/pkgconfig/collectors.go b/pkgconfig/collectors.go
index 094ac805..dcf0a478 100644
--- a/pkgconfig/collectors.go
+++ b/pkgconfig/collectors.go
@@ -1,6 +1,16 @@
 package pkgconfig
 
+import "reflect"
+
 type ConfigCollectors struct {
+	DNSMessage struct {
+		Enable            bool `yaml:"enable"`
+		ChannelBufferSize int  `yaml:"chan-buffer-size"`
+		Matching          struct {
+			Include map[string]interface{} `yaml:"include"`
+			Exclude map[string]interface{} `yaml:"exclude"`
+		} `yaml:"matching"`
+	} `yaml:"dnsmessage"`
 	Tail struct {
 		Enable       bool   `yaml:"enable"`
 		TimeLayout   string `yaml:"time-layout"`
@@ -74,6 +84,9 @@ type ConfigCollectors struct {
 }
 
 func (c *ConfigCollectors) SetDefault() {
+	c.DNSMessage.Enable = false
+	c.DNSMessage.ChannelBufferSize = 65535
+
 	c.Tail.Enable = false
 	c.Tail.TimeLayout = ""
 	c.Tail.PatternQuery = ""
@@ -135,3 +148,24 @@ func (c *ConfigCollectors) SetDefault() {
 	c.Tzsp.ListenPort = 10000
 	c.Tzsp.ChannelBufferSize = 65535
 }
+
+func (c *ConfigCollectors) GetTags() (ret []string) {
+	cl := reflect.TypeOf(*c)
+
+	for i := 0; i < cl.NumField(); i++ {
+		field := cl.Field(i)
+		tag := field.Tag.Get("yaml")
+		ret = append(ret, tag)
+	}
+	return ret
+}
+
+func (c *ConfigCollectors) IsValid(name string) bool {
+	tags := c.GetTags()
+	for i := range tags {
+		if name == tags[i] {
+			return true
+		}
+	}
+	return false
+}
diff --git a/pkgconfig/collectors_test.go b/pkgconfig/collectors_test.go
index 4a21cd61..04538339 100644
--- a/pkgconfig/collectors_test.go
+++ b/pkgconfig/collectors_test.go
@@ -6,6 +6,9 @@ func TestConfigCollectorsSetDefault(t *testing.T) {
 	config := ConfigCollectors{}
 	config.SetDefault()
 
+	if config.DNSMessage.Enable != false {
+		t.Errorf("dnsmessage should be disabled")
+	}
 	if config.Dnstap.Enable != false {
 		t.Errorf("dnstap should be disabled")
 	}
diff --git a/pkgconfig/config.go b/pkgconfig/config.go
index b77a2dd3..1ff9f5a0 100644
--- a/pkgconfig/config.go
+++ b/pkgconfig/config.go
@@ -5,6 +5,7 @@ import (
 	"io"
 	"os"
 	"reflect"
+	"regexp"
 
 	"github.com/pkg/errors"
 	"gopkg.in/yaml.v3"
@@ -28,6 +29,7 @@ type Config struct {
 	Loggers              ConfigLoggers      `yaml:"loggers"`
 	OutgoingTransformers ConfigTransformers `yaml:"loggers-transformers"`
 	Multiplexer          ConfigMultiplexer  `yaml:"multiplexer"`
+	Pipelines            []ConfigPipelines  `yaml:"pipelines"`
 }
 
 func (c *Config) SetDefault() {
@@ -63,7 +65,7 @@ func (c *Config) GetServerIdentity() string {
 	}
 }
 
-func ReloadConfig(configPath string, config *Config) error {
+func ReloadConfig(configPath string, config *Config, refDNSMessage map[string]interface{}) error {
 	// Open config file
 	configFile, err := os.Open(configPath)
 	if err != nil {
@@ -72,7 +74,7 @@ func ReloadConfig(configPath string, config *Config) error {
 	defer configFile.Close()
 
 	// Check config to detect unknown keywords
-	if err := CheckConfig(configPath); err != nil {
+	if err := CheckConfig(configPath, refDNSMessage); err != nil {
 		return err
 	}
 
@@ -86,7 +88,7 @@ func ReloadConfig(configPath string, config *Config) error {
 	return nil
 }
 
-func LoadConfig(configPath string) (*Config, error) {
+func LoadConfig(configPath string, refDNSMessage map[string]interface{}) (*Config, error) {
 	// Open config file
 	configFile, err := os.Open(configPath)
 	if err != nil {
@@ -95,7 +97,7 @@ func LoadConfig(configPath string) (*Config, error) {
 	defer configFile.Close()
 
 	// Check config to detect unknown keywords
-	if err := CheckConfig(configPath); err != nil {
+	if err := CheckConfig(configPath, refDNSMessage); err != nil {
 		return nil, err
 	}
 
@@ -113,14 +115,15 @@ func LoadConfig(configPath string) (*Config, error) {
 	return config, nil
 }
 
-func CheckConfig(userConfigPath string) error {
+func CheckConfig(userConfigPath string, dmRef map[string]interface{}) error {
 	// create default config
-	// and simulate one route, one collector and one logger
+	// and simulate items in multiplexer and pipelines mode
 	defaultConfig := &Config{}
 	defaultConfig.SetDefault()
 	defaultConfig.Multiplexer.Routes = append(defaultConfig.Multiplexer.Routes, MultiplexRoutes{})
 	defaultConfig.Multiplexer.Loggers = append(defaultConfig.Multiplexer.Loggers, MultiplexInOut{})
 	defaultConfig.Multiplexer.Collectors = append(defaultConfig.Multiplexer.Collectors, MultiplexInOut{})
+	defaultConfig.Pipelines = append(defaultConfig.Pipelines, ConfigPipelines{})
 
 	// Convert default config to map
 	// And get unique YAML keys
@@ -130,6 +133,11 @@ func CheckConfig(userConfigPath string) error {
 	}
 	defaultKeywords := getUniqueKeywords(defaultConfigMap)
 
+	// add DNSMessage default keys
+	for k := range dmRef {
+		defaultKeywords[k] = true
+	}
+
 	// Read user configuration file
 	// And get unique YAML keys from user config
 	userConfigMap, err := loadUserConfigToMap(userConfigPath)
@@ -139,7 +147,26 @@ func CheckConfig(userConfigPath string) error {
 	userKeywords := getUniqueKeywords(userConfigMap)
 
 	// Check for unknown keys in user config
+	// ignore dynamic keys as atags.tags.*: google
+
+	// Define regular expressions to match dynamic keys
+	regexPatterns := []string{`\.\*(\.)?`, `\.(\d+)(\.)?`}
+
 	for key := range userKeywords {
+		// Ignore dynamic keys that contain ".*" or .[digits].
+		matched := false
+		for _, pattern := range regexPatterns {
+			match, _ := regexp.MatchString(pattern, key)
+			if match {
+				matched = true
+				break
+			}
+		}
+		if matched {
+			continue
+		}
+
+		// search in default keywords
 		if _, ok := defaultKeywords[key]; !ok {
 			return errors.Errorf("unknown YAML key `%s` in configuration", key)
 		}
@@ -156,11 +183,13 @@ func CheckConfig(userConfigPath string) error {
 func checkKeywordsPosition(nextUserCfg, nextDefCfg map[string]interface{}, defaultConf map[string]interface{}, sectionName string) error {
 	for k, v := range nextUserCfg {
 		// Check if the key is present in the default config
-		if _, ok := nextDefCfg[k]; !ok {
-			if sectionName == "" {
-				return errors.Errorf("invalid key `%s` at root", k)
+		if len(nextDefCfg) > 0 {
+			if _, ok := nextDefCfg[k]; !ok {
+				if sectionName == "" {
+					return errors.Errorf("invalid key `%s` at root", k)
+				}
+				return errors.Errorf("invalid key `%s` in section `%s`", k, sectionName)
 			}
-			return errors.Errorf("invalid key `%s` in section `%s`", k, sectionName)
 		}
 
 		// If the value is a map, recursively check for invalid keywords
@@ -181,6 +210,71 @@ func checkKeywordsPosition(nextUserCfg, nextDefCfg map[string]interface{}, defau
 			}
 		}
 
+		// If the value is a slice and we are in the pipelines part
+		if val.Kind() == reflect.Slice && k == "pipelines" {
+			if err := checkPipelinesConfig(val, nextDefCfg[k].([]interface{}), defaultConf, k); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+func checkPipelinesConfig(currentVal reflect.Value, currentRef []interface{}, defaultConf map[string]interface{}, k string) error {
+	refLoggers := defaultConf[KeyLoggers].(map[string]interface{})
+	refCollectors := defaultConf[KeyCollectors].(map[string]interface{})
+	refTransforms := defaultConf["collectors-transformers"].(map[string]interface{})
+
+	for pos, item := range currentVal.Interface().([]interface{}) {
+		valReflect := reflect.ValueOf(item)
+		refItem := currentRef[0].(map[string]interface{})
+		if valReflect.Kind() == reflect.Map {
+			for _, key := range valReflect.MapKeys() {
+				strKey := key.Interface().(string)
+				mapVal := valReflect.MapIndex(key)
+
+				if _, ok := refItem[strKey]; !ok {
+					// Check if the key exists in neither loggers nor collectors
+					loggerExists := refLoggers[strKey] != nil
+					collectorExists := refCollectors[strKey] != nil
+					if !loggerExists && !collectorExists {
+						return errors.Errorf("invalid `%s` in `%s` pipelines at position %d", strKey, k, pos)
+					}
+
+					// check logger or collectors
+					if loggerExists || collectorExists {
+						nextSectionName := fmt.Sprintf("%s[%d].%s", k, pos, strKey)
+						refMap := refLoggers
+						if collectorExists {
+							refMap = refCollectors
+						}
+						// Type assertion to check if the value is a map
+						if value, ok := mapVal.Interface().(map[string]interface{}); ok {
+							if err := checkKeywordsPosition(value, refMap[strKey].(map[string]interface{}), defaultConf, nextSectionName); err != nil {
+								return err
+							}
+						} else {
+							return errors.Errorf("invalid `%s` value in `%s` pipelines at position %d", strKey, k, pos)
+						}
+					}
+				}
+
+				// Check transforms section
+				// Type assertion to check if the value is a map
+				if strKey == "transforms" {
+					nextSectionName := fmt.Sprintf("%s.%s", k, strKey)
+					if value, ok := mapVal.Interface().(map[string]interface{}); ok {
+						if err := checkKeywordsPosition(value, refTransforms, defaultConf, nextSectionName); err != nil {
+							return err
+						}
+					} else {
+						return errors.Errorf("invalid `%s` value in `%s` pipelines at position %d", strKey, k, pos)
+					}
+				}
+			}
+		} else {
+			return errors.Errorf("invalid item type in pipelines list: %s", valReflect.Kind())
+		}
 	}
 	return nil
 }
@@ -214,7 +308,7 @@ func checkMultiplexerConfig(currentVal reflect.Value, currentRef []interface{},
 						return errors.Errorf("invalid `%s` in `%s` list at position %d", strKey, k, pos)
 					}
 
-					// check logger
+					// check logger or collectors
 					if k == KeyLoggers || k == KeyCollectors {
 						nextSectionName := fmt.Sprintf("%s[%d].%s", k, pos, strKey)
 						refMap := refLoggers
@@ -246,6 +340,8 @@ func checkMultiplexerConfig(currentVal reflect.Value, currentRef []interface{},
 					}
 				}
 			}
+		} else {
+			return errors.Errorf("invalid item type in multiplexer list: %s", valReflect.Kind())
 		}
 	}
 	return nil
diff --git a/pkgconfig/config_test.go b/pkgconfig/config_test.go
index 0cb0c8c6..87055b1e 100644
--- a/pkgconfig/config_test.go
+++ b/pkgconfig/config_test.go
@@ -61,7 +61,8 @@ multiplexer:
 		t.Fatal("Error writing to user configuration file:", err)
 	}
 
-	if err := CheckConfig(userConfigFile.Name()); err != nil {
+	dm := make(map[string]interface{})
+	if err := CheckConfig(userConfigFile.Name(), dm); err != nil {
 		t.Errorf("failed: Unexpected error: %v", err)
 	}
 }
@@ -88,12 +89,47 @@ multiplexer:
 		t.Fatal("Error writing to user configuration file:", err)
 	}
 
+	dm := make(map[string]interface{})
 	expectedError := errors.Errorf("unknown YAML key `unknown-key` in configuration")
-	if err := CheckConfig(userConfigFile.Name()); err == nil || err.Error() != expectedError.Error() {
+	if err := CheckConfig(userConfigFile.Name(), dm); err == nil || err.Error() != expectedError.Error() {
 		t.Errorf("Expected error %v, but got %v", expectedError, err)
 	}
 }
 
+// Ignore dynamic keys
+func TestConfig_CheckConfig_IgnoreDynamicKeys(t *testing.T) {
+	userConfigFile, err := os.CreateTemp("", "user-config.yaml")
+	if err != nil {
+		t.Fatal("Error creating temporary file:", err)
+	}
+	defer os.Remove(userConfigFile.Name())
+	defer userConfigFile.Close()
+
+	userConfigContent := `
+global:
+  trace: false
+pipelines:
+  - name: match
+    dnsmessage:
+      matching:
+        include:
+          atags.tags.*: test
+          atags.tags.2: test
+          dns.resources-records.*: test
+          dns.resources-records.10.rdata: test
+          dns.resources-records.*.ttl: test
+`
+	err = os.WriteFile(userConfigFile.Name(), []byte(userConfigContent), 0644)
+	if err != nil {
+		t.Fatal("Error writing to user configuration file:", err)
+	}
+
+	dm := make(map[string]interface{})
+	if err := CheckConfig(userConfigFile.Name(), dm); err != nil {
+		t.Errorf("Expected no error, but got %v", err)
+	}
+}
+
 // Keywork exist but not at the good position
 func TestConfig_CheckConfig_BadKeywordPosition(t *testing.T) {
 	userConfigFile, err := os.CreateTemp("", "user-config.yaml")
@@ -112,7 +148,9 @@ global:
 	if err != nil {
 		t.Fatal("Error writing to user configuration file:", err)
 	}
-	if err := CheckConfig(userConfigFile.Name()); err == nil {
+
+	dm := make(map[string]interface{})
+	if err := CheckConfig(userConfigFile.Name(), dm); err == nil {
 		t.Errorf("Expected error, but got %v", err)
 	}
 }
@@ -148,7 +186,9 @@ multiplexer:
 	if err != nil {
 		t.Fatal("Error writing to user configuration file:", err)
 	}
-	if err := CheckConfig(userConfigFile.Name()); err != nil {
+
+	dm := make(map[string]interface{})
+	if err := CheckConfig(userConfigFile.Name(), dm); err != nil {
 		t.Errorf("failed: Unexpected error: %v", err)
 	}
 }
@@ -178,7 +218,74 @@ multiplexer:
 	if err != nil {
 		t.Fatal("Error writing to user configuration file:", err)
 	}
-	if err := CheckConfig(userConfigFile.Name()); err == nil {
+
+	dm := make(map[string]interface{})
+	if err := CheckConfig(userConfigFile.Name(), dm); err == nil {
+		t.Errorf("Expected error, but got %v", err)
+	}
+}
+
+// Valid pipeline configuration
+func TestConfig_CheckPipelinesConfig_Valid(t *testing.T) {
+	userConfigFile, err := os.CreateTemp("", "user-config.yaml")
+	if err != nil {
+		t.Fatal("Error creating temporary file:", err)
+	}
+	defer os.Remove(userConfigFile.Name())
+	defer userConfigFile.Close()
+
+	userConfigContent := `
+pipelines:
+- name: dnsdist-main
+  dnstap:
+    listen-ip: 0.0.0.0
+    listen-port: 6000
+  routing-policy: 
+    default: [ console ]
+
+- name: console
+  stdout:
+    mode: text
+`
+	err = os.WriteFile(userConfigFile.Name(), []byte(userConfigContent), 0644)
+	if err != nil {
+		t.Fatal("Error writing to user configuration file:", err)
+	}
+
+	dm := make(map[string]interface{})
+	if err := CheckConfig(userConfigFile.Name(), dm); err != nil {
+		t.Errorf("failed: Unexpected error: %v", err)
+	}
+}
+
+// Invalid pipeline configuration
+func TestConfig_CheckPipelinesConfig_Invalid(t *testing.T) {
+	userConfigFile, err := os.CreateTemp("", "user-config.yaml")
+	if err != nil {
+		t.Fatal("Error creating temporary file:", err)
+	}
+	defer os.Remove(userConfigFile.Name())
+	defer userConfigFile.Close()
+
+	userConfigContent := `
+pipelines:
+- name: dnsdist-main
+  dnstap:
+    listen-ip: 0.0.0.0
+    transforms:
+      normalize:
+        qname-lowercase: true
+  routing-policy: 
+    default: [ console ]
+`
+
+	err = os.WriteFile(userConfigFile.Name(), []byte(userConfigContent), 0644)
+	if err != nil {
+		t.Fatal("Error writing to user configuration file:", err)
+	}
+
+	dm := make(map[string]interface{})
+	if err := CheckConfig(userConfigFile.Name(), dm); err == nil {
 		t.Errorf("Expected error, but got %v", err)
 	}
 }
diff --git a/pkgconfig/loggers.go b/pkgconfig/loggers.go
index e96fd3ac..cc9a33c0 100644
--- a/pkgconfig/loggers.go
+++ b/pkgconfig/loggers.go
@@ -1,6 +1,8 @@
 package pkgconfig
 
 import (
+	"reflect"
+
 	"github.com/dmachard/go-dnscollector/netlib"
 	"github.com/prometheus/prometheus/model/relabel"
 )
@@ -539,5 +541,25 @@ func (c *ConfigLoggers) SetDefault() {
 	c.FalcoClient.Enable = false
 	c.FalcoClient.URL = "http://127.0.0.1:9200"
 	c.FalcoClient.ChannelBufferSize = 65535
+}
+
+func (c *ConfigLoggers) GetTags() (ret []string) {
+	cl := reflect.TypeOf(*c)
+
+	for i := 0; i < cl.NumField(); i++ {
+		field := cl.Field(i)
+		tag := field.Tag.Get("yaml")
+		ret = append(ret, tag)
+	}
+	return ret
+}
 
+func (c *ConfigLoggers) IsValid(name string) bool {
+	tags := c.GetTags()
+	for i := range tags {
+		if name == tags[i] {
+			return true
+		}
+	}
+	return false
 }
diff --git a/pkgconfig/pipelines.go b/pkgconfig/pipelines.go
new file mode 100644
index 00000000..126f743a
--- /dev/null
+++ b/pkgconfig/pipelines.go
@@ -0,0 +1,13 @@
+package pkgconfig
+
+type ConfigPipelines struct {
+	Name          string                 `yaml:"name"`
+	Transforms    map[string]interface{} `yaml:"transforms"`
+	Params        map[string]interface{} `yaml:",inline"`
+	RoutingPolicy PipelinesRouting       `yaml:"routing-policy"`
+}
+
+type PipelinesRouting struct {
+	Default []string `yaml:"default,flow"`
+	Dropped []string `yaml:"dropped,flow"`
+}
diff --git a/pkgconfig/transformers.go b/pkgconfig/transformers.go
index d52b9aa0..33461919 100644
--- a/pkgconfig/transformers.go
+++ b/pkgconfig/transformers.go
@@ -22,13 +22,13 @@ type ConfigTransformers struct {
 		MeasureLatency    bool `yaml:"measure-latency"`
 		UnansweredQueries bool `yaml:"unanswered-queries"`
 		QueriesTimeout    int  `yaml:"queries-timeout"`
-	}
+	} `yaml:"latency"`
 	Reducer struct {
 		Enable                    bool `yaml:"enable"`
 		RepetitiveTrafficDetector bool `yaml:"repetitive-traffic-detector"`
 		QnamePlusOne              bool `yaml:"qname-plus-one"`
 		WatchInterval             int  `yaml:"watch-interval"`
-	}
+	} `yaml:"reducer"`
 	Filtering struct {
 		Enable          bool     `yaml:"enable"`
 		DropFqdnFile    string   `yaml:"drop-fqdn-file"`
@@ -67,6 +67,10 @@ type ConfigTransformers struct {
 		Enable      bool `yaml:"enable"`
 		AddFeatures bool `yaml:"add-features"`
 	} `yaml:"machine-learning"`
+	ATags struct {
+		Enable bool     `yaml:"enable"`
+		Tags   []string `yaml:"tags,flow"`
+	} `yaml:"atags"`
 }
 
 func (c *ConfigTransformers) SetDefault() {
@@ -125,6 +129,9 @@ func (c *ConfigTransformers) SetDefault() {
 
 	c.MachineLearning.Enable = false
 	c.MachineLearning.AddFeatures = false
+
+	c.ATags.Enable = false
+	c.ATags.Tags = []string{}
 }
 
 func GetFakeConfigTransformers() *ConfigTransformers {
diff --git a/multiplexer.go b/pkglinker/multiplexer.go
similarity index 95%
rename from multiplexer.go
rename to pkglinker/multiplexer.go
index 7fc7e0b9..a1202c11 100644
--- a/multiplexer.go
+++ b/pkglinker/multiplexer.go
@@ -1,13 +1,13 @@
-package main
+package pkglinker
 
 import (
 	"fmt"
 	"strings"
 
 	"github.com/dmachard/go-dnscollector/collectors"
-	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/loggers"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-logger"
 	"gopkg.in/yaml.v2"
 )
@@ -79,7 +79,7 @@ func GetItemConfig(section string, config *pkgconfig.Config, item pkgconfig.Mult
 	return subcfg
 }
 
-func InitMultiplexer(mapLoggers map[string]dnsutils.Worker, mapCollectors map[string]dnsutils.Worker, config *pkgconfig.Config, logger *logger.Logger) {
+func InitMultiplexer(mapLoggers map[string]pkgutils.Worker, mapCollectors map[string]pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger) {
 
 	// checking all routes before to continue
 	if err := AreRoutesValid(config); err != nil {
@@ -178,7 +178,7 @@ func InitMultiplexer(mapLoggers map[string]dnsutils.Worker, mapCollectors map[st
 	// here the multiplexer logic
 	// connect collectors between loggers
 	for _, route := range config.Multiplexer.Routes {
-		var logwrks []dnsutils.Worker
+		var logwrks []pkgutils.Worker
 		for _, dst := range route.Dst {
 			if _, ok := mapLoggers[dst]; ok {
 				logwrks = append(logwrks, mapLoggers[dst])
@@ -199,7 +199,7 @@ func InitMultiplexer(mapLoggers map[string]dnsutils.Worker, mapCollectors map[st
 	}
 }
 
-func ReloadMultiplexer(mapLoggers map[string]dnsutils.Worker, mapCollectors map[string]dnsutils.Worker, config *pkgconfig.Config, logger *logger.Logger) {
+func ReloadMultiplexer(mapLoggers map[string]pkgutils.Worker, mapCollectors map[string]pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger) {
 	for _, output := range config.Multiplexer.Loggers {
 		newcfg := GetItemConfig("loggers", config, output)
 		if _, ok := mapLoggers[output.Name]; ok {
diff --git a/multiplexer_test.go b/pkglinker/multiplexer_test.go
similarity index 98%
rename from multiplexer_test.go
rename to pkglinker/multiplexer_test.go
index 5297c8e4..bf62490d 100644
--- a/multiplexer_test.go
+++ b/pkglinker/multiplexer_test.go
@@ -1,4 +1,4 @@
-package main
+package pkglinker
 
 import (
 	"testing"
diff --git a/pkglinker/pipelines.go b/pkglinker/pipelines.go
new file mode 100644
index 00000000..c3e6a421
--- /dev/null
+++ b/pkglinker/pipelines.go
@@ -0,0 +1,243 @@
+package pkglinker
+
+import (
+	"fmt"
+
+	"github.com/dmachard/go-dnscollector/collectors"
+	"github.com/dmachard/go-dnscollector/loggers"
+	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
+	"github.com/dmachard/go-logger"
+	"github.com/pkg/errors"
+	"gopkg.in/yaml.v2"
+)
+
+func GetStanzaConfig(config *pkgconfig.Config, item pkgconfig.ConfigPipelines) *pkgconfig.Config {
+
+	cfg := make(map[string]interface{})
+	section := "collectors"
+
+	// Enable the provided collector or loggers
+	for k, p := range item.Params {
+		// is a logger or collector ?
+		if !config.Loggers.IsValid(k) && !config.Collectors.IsValid(k) {
+			panic(fmt.Sprintln("main - get stanza config error"))
+		}
+		if config.Loggers.IsValid(k) {
+			section = "loggers"
+		}
+		if p == nil {
+			item.Params[k] = make(map[string]interface{})
+		}
+		item.Params[k].(map[string]interface{})["enable"] = true
+
+		// ignore other keys
+		break
+	}
+
+	// prepare a new config
+	subcfg := &pkgconfig.Config{}
+	subcfg.SetDefault()
+
+	cfg[section] = item.Params
+	cfg[section+"-transformers"] = make(map[string]interface{})
+
+	// add transformers
+	for k, v := range item.Transforms {
+		v.(map[string]interface{})["enable"] = true
+		cfg[section+"-transformers"].(map[string]interface{})[k] = v
+	}
+
+	// copy global config
+	subcfg.Global = config.Global
+
+	yamlcfg, _ := yaml.Marshal(cfg)
+	if err := yaml.Unmarshal(yamlcfg, subcfg); err != nil {
+		panic(fmt.Sprintf("main - yaml logger config error: %v", err))
+	}
+
+	return subcfg
+}
+
+func StanzaNameIsUniq(name string, config *pkgconfig.Config) (ret error) {
+	stanzaCounter := 0
+	for _, stanza := range config.Pipelines {
+		if name == stanza.Name {
+			stanzaCounter += 1
+		}
+	}
+
+	if stanzaCounter > 1 {
+		return fmt.Errorf("stanza=%s allready exists", name)
+	}
+	return nil
+}
+
+func IsRouteExist(target string, config *pkgconfig.Config) (ret error) {
+	for _, stanza := range config.Pipelines {
+		if target == stanza.Name {
+			return nil
+		}
+	}
+	return fmt.Errorf("route=%s doest not exist", target)
+}
+
+func CreateRouting(stanza pkgconfig.ConfigPipelines, mapCollectors map[string]pkgutils.Worker, mapLoggers map[string]pkgutils.Worker, logger *logger.Logger) {
+	var currentStanza pkgutils.Worker
+	if collector, ok := mapCollectors[stanza.Name]; ok {
+		currentStanza = collector
+	}
+	if logger, ok := mapLoggers[stanza.Name]; ok {
+		currentStanza = logger
+	}
+
+	// default routing
+	for _, route := range stanza.RoutingPolicy.Default {
+		if _, ok := mapCollectors[route]; ok {
+			currentStanza.AddDefaultRoute(mapCollectors[route])
+			logger.Info("main - routing (policy=default) stanza=[%s] to stanza=[%s]", stanza.Name, route)
+		} else if _, ok := mapLoggers[route]; ok {
+			currentStanza.AddDefaultRoute(mapLoggers[route])
+			logger.Info("main - routing (policy=default) stanza=[%s] to stanza=[%s]", stanza.Name, route)
+		} else {
+			logger.Error("main - default routing error from stanza=%s to stanza=%s doest not exist", stanza.Name, route)
+			break
+		}
+	}
+
+	// dropped routing
+	for _, route := range stanza.RoutingPolicy.Dropped {
+		if _, ok := mapCollectors[route]; ok {
+			currentStanza.AddDroppedRoute(mapCollectors[route])
+			logger.Info("main - routing (policy=dropped) stanza=[%s] to stanza=[%s]", stanza.Name, route)
+		} else if _, ok := mapLoggers[route]; ok {
+			currentStanza.AddDroppedRoute(mapLoggers[route])
+			logger.Info("main - routing (policy=dropped) stanza=[%s] to stanza=[%s]", stanza.Name, route)
+		} else {
+			logger.Error("main - routing error with dropped messages from stanza=%s to stanza=%s doest not exist", stanza.Name, route)
+			break
+		}
+	}
+}
+
+func CreateStanza(stanzaName string, config *pkgconfig.Config, mapCollectors map[string]pkgutils.Worker, mapLoggers map[string]pkgutils.Worker, logger *logger.Logger) {
+	// register the logger if enabled
+	if config.Loggers.RestAPI.Enable {
+		mapLoggers[stanzaName] = loggers.NewRestAPI(config, logger, stanzaName)
+	}
+	if config.Loggers.Prometheus.Enable {
+		mapLoggers[stanzaName] = loggers.NewPrometheus(config, logger, stanzaName)
+	}
+	if config.Loggers.Stdout.Enable {
+		mapLoggers[stanzaName] = loggers.NewStdOut(config, logger, stanzaName)
+	}
+	if config.Loggers.LogFile.Enable {
+		mapLoggers[stanzaName] = loggers.NewLogFile(config, logger, stanzaName)
+	}
+	if config.Loggers.DNSTap.Enable {
+		mapLoggers[stanzaName] = loggers.NewDnstapSender(config, logger, stanzaName)
+	}
+	if config.Loggers.TCPClient.Enable {
+		mapLoggers[stanzaName] = loggers.NewTCPClient(config, logger, stanzaName)
+	}
+	if config.Loggers.Syslog.Enable {
+		mapLoggers[stanzaName] = loggers.NewSyslog(config, logger, stanzaName)
+	}
+	if config.Loggers.Fluentd.Enable {
+		mapLoggers[stanzaName] = loggers.NewFluentdClient(config, logger, stanzaName)
+	}
+	if config.Loggers.InfluxDB.Enable {
+		mapLoggers[stanzaName] = loggers.NewInfluxDBClient(config, logger, stanzaName)
+	}
+	if config.Loggers.LokiClient.Enable {
+		mapLoggers[stanzaName] = loggers.NewLokiClient(config, logger, stanzaName)
+	}
+	if config.Loggers.Statsd.Enable {
+		mapLoggers[stanzaName] = loggers.NewStatsdClient(config, logger, stanzaName)
+	}
+	if config.Loggers.ElasticSearchClient.Enable {
+		mapLoggers[stanzaName] = loggers.NewElasticSearchClient(config, logger, stanzaName)
+	}
+	if config.Loggers.ScalyrClient.Enable {
+		mapLoggers[stanzaName] = loggers.NewScalyrClient(config, logger, stanzaName)
+	}
+	if config.Loggers.RedisPub.Enable {
+		mapLoggers[stanzaName] = loggers.NewRedisPub(config, logger, stanzaName)
+	}
+	if config.Loggers.KafkaProducer.Enable {
+		mapLoggers[stanzaName] = loggers.NewKafkaProducer(config, logger, stanzaName)
+	}
+	if config.Loggers.FalcoClient.Enable {
+		mapLoggers[stanzaName] = loggers.NewFalcoClient(config, logger, stanzaName)
+	}
+
+	// register the collector if enabled
+	if config.Collectors.DNSMessage.Enable {
+		mapCollectors[stanzaName] = collectors.NewDNSMessage(nil, config, logger, stanzaName)
+	}
+	if config.Collectors.Dnstap.Enable {
+		mapCollectors[stanzaName] = collectors.NewDnstap(nil, config, logger, stanzaName)
+	}
+	if config.Collectors.DnstapProxifier.Enable {
+		mapCollectors[stanzaName] = collectors.NewDnstapProxifier(nil, config, logger, stanzaName)
+	}
+	if config.Collectors.AfpacketLiveCapture.Enable {
+		mapCollectors[stanzaName] = collectors.NewAfpacketSniffer(nil, config, logger, stanzaName)
+	}
+	if config.Collectors.XdpLiveCapture.Enable {
+		mapCollectors[stanzaName] = collectors.NewXDPSniffer(nil, config, logger, stanzaName)
+	}
+	if config.Collectors.Tail.Enable {
+		mapCollectors[stanzaName] = collectors.NewTail(nil, config, logger, stanzaName)
+	}
+	if config.Collectors.PowerDNS.Enable {
+		mapCollectors[stanzaName] = collectors.NewProtobufPowerDNS(nil, config, logger, stanzaName)
+	}
+	if config.Collectors.FileIngestor.Enable {
+		mapCollectors[stanzaName] = collectors.NewFileIngestor(nil, config, logger, stanzaName)
+	}
+	if config.Collectors.Tzsp.Enable {
+		mapCollectors[stanzaName] = collectors.NewTZSP(nil, config, logger, stanzaName)
+	}
+}
+
+func InitPipelines(mapLoggers map[string]pkgutils.Worker, mapCollectors map[string]pkgutils.Worker, config *pkgconfig.Config, logger *logger.Logger) error {
+	// check if the name of each stanza is uniq
+	for _, stanza := range config.Pipelines {
+		if err := StanzaNameIsUniq(stanza.Name, config); err != nil {
+			return errors.Errorf("stanza with name=[%s] is duplicated", stanza.Name)
+		}
+	}
+
+	// check if all routes exists before continue
+	for _, stanza := range config.Pipelines {
+		for _, route := range stanza.RoutingPolicy.Default {
+			if err := IsRouteExist(route, config); err != nil {
+				return errors.Errorf("stanza=[%s] default route=[%s] doest not exist", stanza.Name, route)
+			}
+		}
+		for _, route := range stanza.RoutingPolicy.Dropped {
+			if err := IsRouteExist(route, config); err != nil {
+				return errors.Errorf("stanza=[%s] dropped route=[%s] doest not exist", stanza.Name, route)
+			}
+		}
+	}
+
+	// read each stanza and init
+	for _, stanza := range config.Pipelines {
+		stanzaConfig := GetStanzaConfig(config, stanza)
+		CreateStanza(stanza.Name, stanzaConfig, mapCollectors, mapLoggers, logger)
+
+	}
+
+	// create routing
+	for _, stanza := range config.Pipelines {
+		if mapCollectors[stanza.Name] != nil || mapLoggers[stanza.Name] != nil {
+			CreateRouting(stanza, mapCollectors, mapLoggers, logger)
+		} else {
+			return errors.Errorf("stanza=[%v] doest not exist", stanza.Name)
+		}
+	}
+
+	return nil
+}
diff --git a/pkglinker/pipelines_test.go b/pkglinker/pipelines_test.go
new file mode 100644
index 00000000..dd62b589
--- /dev/null
+++ b/pkglinker/pipelines_test.go
@@ -0,0 +1,53 @@
+package pkglinker
+
+import (
+	"testing"
+
+	"github.com/dmachard/go-dnscollector/pkgconfig"
+)
+
+func TestPipeline_IsRouteExist(t *testing.T) {
+	// Create a mock configuration for testing
+	config := &pkgconfig.Config{}
+	config.Pipelines = []pkgconfig.ConfigPipelines{
+		{Name: "validroute"},
+	}
+
+	// Case where the route exists
+	existingRoute := "validroute"
+	err := IsRouteExist(existingRoute, config)
+	if err != nil {
+		t.Errorf("For the existing route %s, an unexpected error was returned: %v", existingRoute, err)
+	}
+
+	// Case where the route does not exist
+	nonExistingRoute := "non-existent-route"
+	err = IsRouteExist(nonExistingRoute, config)
+	if err == nil {
+		t.Errorf("For the non-existing route %s, an expected error was not returned. Received error: %v", nonExistingRoute, err)
+	}
+}
+
+func TestPipeline_StanzaNameIsUniq(t *testing.T) {
+	// Create a mock configuration for testing
+	config := &pkgconfig.Config{}
+	config.Pipelines = []pkgconfig.ConfigPipelines{
+		{Name: "unique-stanza"},
+		{Name: "duplicate-stanza"},
+		{Name: "duplicate-stanza"},
+	}
+
+	// Case where the stanza name is unique
+	uniqueStanzaName := "unique-stanza"
+	err := StanzaNameIsUniq(uniqueStanzaName, config)
+	if err != nil {
+		t.Errorf("For the unique stanza name %s, an unexpected error was returned: %v", uniqueStanzaName, err)
+	}
+
+	// Case where the stanza name is not unique
+	duplicateStanzaName := "duplicate-stanza"
+	err = StanzaNameIsUniq(duplicateStanzaName, config)
+	if err == nil {
+		t.Errorf("For the duplicate stanza name %s, an expected error was not returned. Received error: %v", duplicateStanzaName, err)
+	}
+}
diff --git a/pkgutils/routing.go b/pkgutils/routing.go
new file mode 100644
index 00000000..85d813d0
--- /dev/null
+++ b/pkgutils/routing.go
@@ -0,0 +1,125 @@
+package pkgutils
+
+import (
+	"time"
+
+	"github.com/dmachard/go-dnscollector/dnsutils"
+	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-logger"
+)
+
+func GetRoutes(routes []Worker) ([]chan dnsutils.DNSMessage, []string) {
+	channels := []chan dnsutils.DNSMessage{}
+	names := []string{}
+	for _, p := range routes {
+		if c := p.GetInputChannel(); c != nil {
+			channels = append(channels, c)
+			names = append(names, p.GetName())
+		} else {
+			panic("default routing to stanza=[" + p.GetName() + "] not supported")
+		}
+	}
+	return channels, names
+}
+
+type RoutingHandler struct {
+	name          string
+	logger        *logger.Logger
+	config        *pkgconfig.Config
+	stopRun       chan bool
+	doneRun       chan bool
+	droppedCount  map[string]int
+	dropped       chan string
+	droppedRoutes []Worker
+	defaultRoutes []Worker
+}
+
+func NewRoutingHandler(config *pkgconfig.Config, console *logger.Logger, name string) RoutingHandler {
+	rh := RoutingHandler{
+		name:    name,
+		logger:  console,
+		config:  config,
+		stopRun: make(chan bool),
+		doneRun: make(chan bool),
+	}
+	go rh.Run()
+	return rh
+}
+func (rh *RoutingHandler) LogInfo(msg string, v ...interface{}) {
+	rh.logger.Info("["+rh.name+"] "+msg, v...)
+}
+
+func (rh *RoutingHandler) LogError(msg string, v ...interface{}) {
+	rh.logger.Error("["+rh.name+"] "+msg, v...)
+}
+
+func (rh *RoutingHandler) LogFatal(msg string) {
+	rh.logger.Error("[" + rh.name + "] " + msg)
+}
+
+func (rh *RoutingHandler) AddDroppedRoute(wrk Worker) {
+	rh.droppedRoutes = append(rh.droppedRoutes, wrk)
+}
+
+func (rh *RoutingHandler) AddDefaultRoute(wrk Worker) {
+	rh.defaultRoutes = append(rh.defaultRoutes, wrk)
+}
+
+func (rh *RoutingHandler) SetDefaultRoutes(workers []Worker) {
+	rh.defaultRoutes = workers
+}
+
+func (rh *RoutingHandler) GetDefaultRoutes() ([]chan dnsutils.DNSMessage, []string) {
+	return GetRoutes(rh.defaultRoutes)
+}
+
+func (rh *RoutingHandler) GetDroppedRoutes() ([]chan dnsutils.DNSMessage, []string) {
+	return GetRoutes(rh.droppedRoutes)
+}
+
+func (rh *RoutingHandler) Stop() {
+	rh.LogInfo("stopping routing handler...")
+	rh.stopRun <- true
+	<-rh.doneRun
+
+	rh.LogInfo("routing handler stopped")
+}
+
+func (rh *RoutingHandler) Run() {
+	rh.LogInfo("starting routing handler...")
+	nextBufferInterval := 10 * time.Second
+	nextBufferFull := time.NewTimer(nextBufferInterval)
+
+RUN_LOOP:
+	for {
+		select {
+		case <-rh.stopRun:
+			rh.doneRun <- true
+			break RUN_LOOP
+		case stanzaName := <-rh.dropped:
+			if _, ok := rh.droppedCount[stanzaName]; !ok {
+				rh.droppedCount[stanzaName] = 1
+			} else {
+				rh.droppedCount[stanzaName]++
+			}
+		case <-nextBufferFull.C:
+			for v, k := range rh.droppedCount {
+				if k > 0 {
+					rh.LogError("stanza[%s] buffer is full, %d packet(s) dropped", v, k)
+					rh.droppedCount[v] = 0
+				}
+			}
+			nextBufferFull.Reset(nextBufferInterval)
+		}
+	}
+}
+
+func (rh *RoutingHandler) SendTo(routes []chan dnsutils.DNSMessage, routesName []string, dm dnsutils.DNSMessage) {
+	for i := range routes {
+		select {
+		case routes[i] <- dm:
+		default:
+			rh.dropped <- routesName[i]
+		}
+	}
+}
diff --git a/pkgutils/utils.go b/pkgutils/utils.go
new file mode 100644
index 00000000..77086425
--- /dev/null
+++ b/pkgutils/utils.go
@@ -0,0 +1,18 @@
+package pkgutils
+
+import (
+	"github.com/dmachard/go-dnscollector/dnsutils"
+	"github.com/dmachard/go-dnscollector/pkgconfig"
+)
+
+type Worker interface {
+	AddDefaultRoute(wrk Worker)
+	AddDroppedRoute(wrk Worker)
+	SetLoggers(loggers []Worker)
+	GetName() string
+	Stop()
+	Run()
+	GetInputChannel() chan dnsutils.DNSMessage
+	ReadConfig()
+	ReloadConfig(config *pkgconfig.Config)
+}
diff --git a/processors/dns.go b/processors/dns.go
index 9b3d6a6f..f1691192 100644
--- a/processors/dns.go
+++ b/processors/dns.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/transformers"
 	"github.com/dmachard/go-logger"
 	"github.com/miekg/dns"
@@ -18,33 +19,31 @@ func GetFakeDNS() ([]byte, error) {
 }
 
 type DNSProcessor struct {
-	doneRun      chan bool
-	stopRun      chan bool
-	doneMonitor  chan bool
-	stopMonitor  chan bool
-	recvFrom     chan dnsutils.DNSMessage
-	logger       *logger.Logger
-	config       *pkgconfig.Config
-	ConfigChan   chan *pkgconfig.Config
-	name         string
-	dropped      chan string
-	droppedCount map[string]int
+	doneRun        chan bool
+	stopRun        chan bool
+	doneMonitor    chan bool
+	stopMonitor    chan bool
+	recvFrom       chan dnsutils.DNSMessage
+	logger         *logger.Logger
+	config         *pkgconfig.Config
+	ConfigChan     chan *pkgconfig.Config
+	name           string
+	RoutingHandler pkgutils.RoutingHandler
 }
 
 func NewDNSProcessor(config *pkgconfig.Config, logger *logger.Logger, name string, size int) DNSProcessor {
 	logger.Info("[%s] processor=dns - initialization...", name)
 	d := DNSProcessor{
-		doneMonitor:  make(chan bool),
-		doneRun:      make(chan bool),
-		stopMonitor:  make(chan bool),
-		stopRun:      make(chan bool),
-		recvFrom:     make(chan dnsutils.DNSMessage, size),
-		logger:       logger,
-		config:       config,
-		ConfigChan:   make(chan *pkgconfig.Config),
-		name:         name,
-		dropped:      make(chan string),
-		droppedCount: map[string]int{},
+		doneMonitor:    make(chan bool),
+		doneRun:        make(chan bool),
+		stopMonitor:    make(chan bool),
+		stopRun:        make(chan bool),
+		recvFrom:       make(chan dnsutils.DNSMessage, size),
+		logger:         logger,
+		config:         config,
+		ConfigChan:     make(chan *pkgconfig.Config),
+		name:           name,
+		RoutingHandler: pkgutils.NewRoutingHandler(config, logger, name),
 	}
 	return d
 }
@@ -68,55 +67,21 @@ func (d *DNSProcessor) GetChannelList() []chan dnsutils.DNSMessage {
 }
 
 func (d *DNSProcessor) Stop() {
+	d.LogInfo("stopping routing handler...")
+	d.RoutingHandler.Stop()
+
 	d.LogInfo("stopping to process...")
 	d.stopRun <- true
 	<-d.doneRun
-
-	d.LogInfo("stopping to monitor loggers...")
-	d.stopMonitor <- true
-	<-d.doneMonitor
 }
 
-func (d *DNSProcessor) MonitorLoggers() {
-	watchInterval := 10 * time.Second
-	bufferFull := time.NewTimer(watchInterval)
-FOLLOW_LOOP:
-	for {
-		select {
-		case <-d.stopMonitor:
-			close(d.dropped)
-			bufferFull.Stop()
-			d.doneMonitor <- true
-			break FOLLOW_LOOP
-
-		case loggerName := <-d.dropped:
-			if _, ok := d.droppedCount[loggerName]; !ok {
-				d.droppedCount[loggerName] = 1
-			} else {
-				d.droppedCount[loggerName]++
-			}
-
-		case <-bufferFull.C:
-
-			for v, k := range d.droppedCount {
-				if k > 0 {
-					d.LogError("logger[%s] buffer is full, %d packet(s) dropped", v, k)
-					d.droppedCount[v] = 0
-				}
-			}
-			bufferFull.Reset(watchInterval)
-
-		}
-	}
-	d.LogInfo("monitor terminated")
-}
+func (d *DNSProcessor) Run(defaultWorkers []pkgutils.Worker, droppedworkers []pkgutils.Worker) {
+	// prepare next channels
+	defaultRoutes, defaultNames := pkgutils.GetRoutes(defaultWorkers)
+	droppedRoutes, droppedNames := pkgutils.GetRoutes(droppedworkers)
 
-func (d *DNSProcessor) Run(loggersChannel []chan dnsutils.DNSMessage, loggersName []string) {
 	// prepare enabled transformers
-	transforms := transformers.NewTransforms(&d.config.IngoingTransformers, d.logger, d.name, loggersChannel, 0)
-
-	// start goroutine to count dropped messsages
-	go d.MonitorLoggers()
+	transforms := transformers.NewTransforms(&d.config.IngoingTransformers, d.logger, d.name, defaultRoutes, 0)
 
 	// read incoming dns message
 	d.LogInfo("waiting dns message to process...")
@@ -180,6 +145,7 @@ RUN_LOOP:
 
 			// apply all enabled transformers
 			if transforms.ProcessMessage(&dm) == transformers.ReturnDrop {
+				d.RoutingHandler.SendTo(droppedRoutes, droppedNames, dm)
 				continue
 			}
 
@@ -187,13 +153,8 @@ RUN_LOOP:
 			dm.DNSTap.LatencySec = fmt.Sprintf("%.6f", dm.DNSTap.Latency)
 
 			// dispatch dns message to all generators
-			for i := range loggersChannel {
-				select {
-				case loggersChannel[i] <- dm: // Successful send to logger channel
-				default:
-					d.dropped <- loggersName[i]
-				}
-			}
+			d.RoutingHandler.SendTo(defaultRoutes, defaultNames, dm)
+
 		}
 	}
 	d.LogInfo("processing terminated")
diff --git a/processors/dns_test.go b/processors/dns_test.go
index 8cc872b9..fc5927fc 100644
--- a/processors/dns_test.go
+++ b/processors/dns_test.go
@@ -5,7 +5,9 @@ import (
 	"testing"
 
 	"github.com/dmachard/go-dnscollector/dnsutils"
+	"github.com/dmachard/go-dnscollector/loggers"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-logger"
 )
 
@@ -16,15 +18,15 @@ func Test_DnsProcessor(t *testing.T) {
 
 	// init and run the dns processor
 	consumer := NewDNSProcessor(pkgconfig.GetFakeConfig(), logger, "test", 512)
-	chanTo := make(chan dnsutils.DNSMessage, 512)
-	go consumer.Run([]chan dnsutils.DNSMessage{chanTo}, []string{"test"})
+
+	fl := loggers.NewFakeLogger()
+	go consumer.Run([]pkgutils.Worker{fl}, []pkgutils.Worker{fl})
 
 	dm := dnsutils.GetFakeDNSMessageWithPayload()
 	consumer.GetChannel() <- dm
 
 	// read dns message from dnstap consumer
-	dmOut := <-chanTo
-
+	dmOut := <-fl.GetInputChannel()
 	if dmOut.DNS.Qname != "dnscollector.dev" {
 		t.Errorf("invalid qname in dns message: %s", dm.DNS.Qname)
 	}
diff --git a/processors/dnstap.go b/processors/dnstap.go
index ffaa73b2..67839c73 100644
--- a/processors/dnstap.go
+++ b/processors/dnstap.go
@@ -9,6 +9,7 @@ import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/netlib"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/transformers"
 	"github.com/dmachard/go-dnstap-protobuf"
 	"github.com/dmachard/go-logger"
@@ -51,38 +52,38 @@ func GetFakeDNSTap(dnsquery []byte) *dnstap.Dnstap {
 }
 
 type DNSTapProcessor struct {
-	ConnID       int
-	doneRun      chan bool
-	stopRun      chan bool
-	doneMonitor  chan bool
-	stopMonitor  chan bool
-	recvFrom     chan []byte
-	logger       *logger.Logger
-	config       *pkgconfig.Config
-	ConfigChan   chan *pkgconfig.Config
-	name         string
-	chanSize     int
-	dropped      chan string
-	droppedCount map[string]int
+	ConnID         int
+	doneRun        chan bool
+	stopRun        chan bool
+	doneMonitor    chan bool
+	stopMonitor    chan bool
+	recvFrom       chan []byte
+	logger         *logger.Logger
+	config         *pkgconfig.Config
+	ConfigChan     chan *pkgconfig.Config
+	name           string
+	chanSize       int
+	RoutingHandler pkgutils.RoutingHandler
 }
 
 func NewDNSTapProcessor(connID int, config *pkgconfig.Config, logger *logger.Logger, name string, size int) DNSTapProcessor {
 	logger.Info("[%s] processor=dnstap#%d - initialization...", name, connID)
 
 	d := DNSTapProcessor{
-		ConnID:       connID,
-		doneMonitor:  make(chan bool),
-		doneRun:      make(chan bool),
-		stopMonitor:  make(chan bool),
-		stopRun:      make(chan bool),
-		recvFrom:     make(chan []byte, size),
-		chanSize:     size,
-		logger:       logger,
-		config:       config,
-		ConfigChan:   make(chan *pkgconfig.Config),
-		name:         name,
-		dropped:      make(chan string),
-		droppedCount: map[string]int{},
+		ConnID:      connID,
+		doneMonitor: make(chan bool),
+		doneRun:     make(chan bool),
+		stopMonitor: make(chan bool),
+		stopRun:     make(chan bool),
+		recvFrom:    make(chan []byte, size),
+		chanSize:    size,
+		logger:      logger,
+		config:      config,
+		ConfigChan:  make(chan *pkgconfig.Config),
+		name:        name,
+		// dropped:      make(chan string),
+		// droppedCount: map[string]int{},
+		RoutingHandler: pkgutils.NewRoutingHandler(config, logger, name),
 	}
 
 	return d
@@ -113,56 +114,64 @@ func (d *DNSTapProcessor) GetChannel() chan []byte {
 }
 
 func (d *DNSTapProcessor) Stop() {
+	d.LogInfo("stopping routing handler...")
+	d.RoutingHandler.Stop()
+
 	d.LogInfo("stopping to process...")
 	d.stopRun <- true
 	<-d.doneRun
 
-	d.LogInfo("stopping to monitor loggers...")
-	d.stopMonitor <- true
-	<-d.doneMonitor
-}
-
-func (d *DNSTapProcessor) MonitorLoggers() {
-	watchInterval := 10 * time.Second
-	bufferFull := time.NewTimer(watchInterval)
-MONITOR_LOOP:
-	for {
-		select {
-		case <-d.stopMonitor:
-			close(d.dropped)
-			bufferFull.Stop()
-			d.doneMonitor <- true
-			break MONITOR_LOOP
-
-		case loggerName := <-d.dropped:
-			if _, ok := d.droppedCount[loggerName]; !ok {
-				d.droppedCount[loggerName] = 1
-			} else {
-				d.droppedCount[loggerName]++
-			}
-
-		case <-bufferFull.C:
-			for v, k := range d.droppedCount {
-				if k > 0 {
-					d.LogError("logger[%s] buffer is full, %d packet(s) dropped", v, k)
-					d.droppedCount[v] = 0
-				}
-			}
-			bufferFull.Reset(watchInterval)
-
-		}
-	}
-	d.LogInfo("monitor terminated")
+	// d.LogInfo("stopping to monitor loggers...")
+	// d.stopMonitor <- true
+	// <-d.doneMonitor
 }
 
-func (d *DNSTapProcessor) Run(loggersChannel []chan dnsutils.DNSMessage, loggersName []string) {
+// func (d *DNSTapProcessor) MonitorLoggers() {
+// 	// watchInterval := 10 * time.Second
+// 	// bufferFull := time.NewTimer(watchInterval)
+// MONITOR_LOOP:
+// 	for {
+// 		select {
+// 		case <-d.stopMonitor:
+// 			// close(d.dropped)
+// 			// bufferFull.Stop()
+// 			d.doneMonitor <- true
+// 			break MONITOR_LOOP
+
+// case loggerName := <-d.dropped:
+// 	if _, ok := d.droppedCount[loggerName]; !ok {
+// 		d.droppedCount[loggerName] = 1
+// 	} else {
+// 		d.droppedCount[loggerName]++
+// 	}
+
+// case <-bufferFull.C:
+// 	for v, k := range d.droppedCount {
+// 		if k > 0 {
+// 			d.LogError("logger[%s] buffer is full, %d packet(s) dropped", v, k)
+// 			d.droppedCount[v] = 0
+// 		}
+// 	}
+// 	bufferFull.Reset(watchInterval)
+
+// 		}
+// 	}
+// 	d.LogInfo("monitor terminated")
+// }
+
+// func (d *DNSTapProcessor) Run(loggersChannel []chan dnsutils.DNSMessage, loggersName []string) {
+func (d *DNSTapProcessor) Run(defaultWorkers []pkgutils.Worker, droppedworkers []pkgutils.Worker) {
 	dt := &dnstap.Dnstap{}
 
+	// prepare next channels
+	defaultRoutes, defaultNames := pkgutils.GetRoutes(defaultWorkers)
+	droppedRoutes, droppedNames := pkgutils.GetRoutes(droppedworkers)
+
 	// prepare enabled transformers
-	transforms := transformers.NewTransforms(&d.config.IngoingTransformers, d.logger, d.name, loggersChannel, d.ConnID)
+	transforms := transformers.NewTransforms(&d.config.IngoingTransformers, d.logger, d.name, defaultRoutes, d.ConnID)
 
 	// start goroutine to count dropped messsages
-	go d.MonitorLoggers()
+	// go d.MonitorLoggers()
 
 	// read incoming dns message
 	d.LogInfo("waiting dns message to process...")
@@ -283,20 +292,15 @@ RUN_LOOP:
 
 			// apply all enabled transformers
 			if transforms.ProcessMessage(&dm) == transformers.ReturnDrop {
+				d.RoutingHandler.SendTo(droppedRoutes, droppedNames, dm)
 				continue
 			}
 
 			// convert latency to human
 			dm.DNSTap.LatencySec = fmt.Sprintf("%.6f", dm.DNSTap.Latency)
 
-			// dispatch dns message to connected loggers
-			for i := range loggersChannel {
-				select {
-				case loggersChannel[i] <- dm: // Successful send to logger channel
-				default:
-					d.dropped <- loggersName[i]
-				}
-			}
+			// dispatch dns message to connected routes
+			d.RoutingHandler.SendTo(defaultRoutes, defaultNames, dm)
 
 		}
 	}
diff --git a/processors/dnstap_test.go b/processors/dnstap_test.go
index 99405173..1f981079 100644
--- a/processors/dnstap_test.go
+++ b/processors/dnstap_test.go
@@ -4,8 +4,9 @@ import (
 	"bytes"
 	"testing"
 
-	"github.com/dmachard/go-dnscollector/dnsutils"
+	"github.com/dmachard/go-dnscollector/loggers"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnstap-protobuf"
 	"github.com/dmachard/go-logger"
 	"github.com/miekg/dns"
@@ -19,7 +20,7 @@ func Test_DnstapProcessor(t *testing.T) {
 
 	// init the dnstap consumer
 	consumer := NewDNSTapProcessor(0, pkgconfig.GetFakeConfig(), logger, "test", 512)
-	chanTo := make(chan dnsutils.DNSMessage, 512)
+	// chanTo := make(chan dnsutils.DNSMessage, 512)
 
 	// prepare dns query
 	dnsmsg := new(dns.Msg)
@@ -36,12 +37,15 @@ func Test_DnstapProcessor(t *testing.T) {
 
 	data, _ := proto.Marshal(dt)
 
-	go consumer.Run([]chan dnsutils.DNSMessage{chanTo}, []string{"test"})
+	// run the consumer with a fake logger
+	fl := loggers.NewFakeLogger()
+	go consumer.Run([]pkgutils.Worker{fl}, []pkgutils.Worker{fl})
+
 	// add packet to consumer
 	consumer.GetChannel() <- data
 
 	// read dns message from dnstap consumer
-	dm := <-chanTo
+	dm := <-fl.GetInputChannel()
 	if dm.DNS.Qname != "www.google.fr" {
 		t.Errorf("invalid qname in dns message: %s", dm.DNS.Qname)
 	}
@@ -54,7 +58,7 @@ func Test_DnstapProcessor_MalformedDnsHeader(t *testing.T) {
 
 	// init the dnstap consumer
 	consumer := NewDNSTapProcessor(0, pkgconfig.GetFakeConfig(), logger, "test", 512)
-	chanTo := make(chan dnsutils.DNSMessage, 512)
+	// chanTo := make(chan dnsutils.DNSMessage, 512)
 
 	// prepare dns query
 	dnsmsg := new(dns.Msg)
@@ -71,12 +75,16 @@ func Test_DnstapProcessor_MalformedDnsHeader(t *testing.T) {
 
 	data, _ := proto.Marshal(dt)
 
-	go consumer.Run([]chan dnsutils.DNSMessage{chanTo}, []string{"test"})
+	// run the consumer with a fake logger
+	fl := loggers.NewFakeLogger()
+	go consumer.Run([]pkgutils.Worker{fl}, []pkgutils.Worker{fl})
+
+	// go consumer.Run([]chan dnsutils.DNSMessage{chanTo}, []string{"test"})
 	// add packet to consumer
 	consumer.GetChannel() <- data
 
 	// read dns message from dnstap consumer
-	dm := <-chanTo
+	dm := <-fl.GetInputChannel()
 	if dm.DNS.MalformedPacket == false {
 		t.Errorf("malformed packet not detected")
 	}
@@ -89,7 +97,7 @@ func Test_DnstapProcessor_MalformedDnsQuestion(t *testing.T) {
 
 	// init the dnstap consumer
 	consumer := NewDNSTapProcessor(0, pkgconfig.GetFakeConfig(), logger, "test", 512)
-	chanTo := make(chan dnsutils.DNSMessage, 512)
+	// chanTo := make(chan dnsutils.DNSMessage, 512)
 
 	// prepare dns query
 	dnsquestion := []byte{88, 27, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 15, 100, 110, 115, 116, 97, 112,
@@ -105,12 +113,16 @@ func Test_DnstapProcessor_MalformedDnsQuestion(t *testing.T) {
 
 	data, _ := proto.Marshal(dt)
 
-	go consumer.Run([]chan dnsutils.DNSMessage{chanTo}, []string{"test"})
+	// run the consumer with a fake logger
+	fl := loggers.NewFakeLogger()
+	go consumer.Run([]pkgutils.Worker{fl}, []pkgutils.Worker{fl})
+
+	// go consumer.Run([]chan dnsutils.DNSMessage{chanTo}, []string{"test"})
 	// add packet to consumer
 	consumer.GetChannel() <- data
 
 	// read dns message from dnstap consumer
-	dm := <-chanTo
+	dm := <-fl.GetInputChannel()
 	if dm.DNS.MalformedPacket == false {
 		t.Errorf("malformed packet not detected")
 	}
@@ -123,7 +135,7 @@ func Test_DnstapProcessor_MalformedDnsAnswer(t *testing.T) {
 
 	// init the dnstap consumer
 	consumer := NewDNSTapProcessor(0, pkgconfig.GetFakeConfig(), logger, "test", 512)
-	chanTo := make(chan dnsutils.DNSMessage, 512)
+	// chanTo := make(chan dnsutils.DNSMessage, 512)
 
 	// prepare dns query
 	dnsanswer := []byte{46, 172, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 15, 100, 110, 115, 116, 97, 112, 99, 111, 108, 108, 101, 99, 116,
@@ -140,12 +152,16 @@ func Test_DnstapProcessor_MalformedDnsAnswer(t *testing.T) {
 
 	data, _ := proto.Marshal(dt)
 
-	go consumer.Run([]chan dnsutils.DNSMessage{chanTo}, []string{"test"})
+	// run the consumer with a fake logger
+	fl := loggers.NewFakeLogger()
+	go consumer.Run([]pkgutils.Worker{fl}, []pkgutils.Worker{fl})
+
+	// go consumer.Run([]chan dnsutils.DNSMessage{chanTo}, []string{"test"})
 	// add packet to consumer
 	consumer.GetChannel() <- data
 
 	// read dns message from dnstap consumer
-	dm := <-chanTo
+	dm := <-fl.GetInputChannel()
 	if dm.DNS.MalformedPacket == false {
 		t.Errorf("malformed packet not detected")
 	}
@@ -161,7 +177,7 @@ func Test_DnstapProcessor_DisableDNSParser(t *testing.T) {
 	cfg.Collectors.Dnstap.DisableDNSParser = true
 
 	consumer := NewDNSTapProcessor(0, cfg, logger, "test", 512)
-	chanTo := make(chan dnsutils.DNSMessage, 512)
+	// chanTo := make(chan dnsutils.DNSMessage, 512)
 
 	// prepare dns query
 	dnsmsg := new(dns.Msg)
@@ -178,12 +194,16 @@ func Test_DnstapProcessor_DisableDNSParser(t *testing.T) {
 
 	data, _ := proto.Marshal(dt)
 
-	go consumer.Run([]chan dnsutils.DNSMessage{chanTo}, []string{"test"})
+	// run the consumer with a fake logger
+	fl := loggers.NewFakeLogger()
+	go consumer.Run([]pkgutils.Worker{fl}, []pkgutils.Worker{fl})
+
+	// go consumer.Run([]chan dnsutils.DNSMessage{chanTo}, []string{"test"})
 	// add packet to consumer
 	consumer.GetChannel() <- data
 
 	// read dns message from dnstap consumer
-	dm := <-chanTo
+	dm := <-fl.GetInputChannel()
 	if dm.DNS.ID != 0 {
 		t.Errorf("DNS ID should be equal to zero: %d", dm.DNS.ID)
 	}
diff --git a/processors/powerdns.go b/processors/powerdns.go
index 7fd0f629..9d2b317c 100644
--- a/processors/powerdns.go
+++ b/processors/powerdns.go
@@ -10,6 +10,7 @@ import (
 	"github.com/dmachard/go-dnscollector/dnsutils"
 	"github.com/dmachard/go-dnscollector/netlib"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-dnscollector/transformers"
 	"github.com/dmachard/go-logger"
 	powerdns_protobuf "github.com/dmachard/go-powerdns-protobuf"
@@ -27,37 +28,35 @@ var (
 )
 
 type PdnsProcessor struct {
-	ConnID       int
-	doneRun      chan bool
-	stopRun      chan bool
-	doneMonitor  chan bool
-	stopMonitor  chan bool
-	recvFrom     chan []byte
-	logger       *logger.Logger
-	config       *pkgconfig.Config
-	ConfigChan   chan *pkgconfig.Config
-	name         string
-	chanSize     int
-	dropped      chan string
-	droppedCount map[string]int
+	ConnID         int
+	doneRun        chan bool
+	stopRun        chan bool
+	doneMonitor    chan bool
+	stopMonitor    chan bool
+	recvFrom       chan []byte
+	logger         *logger.Logger
+	config         *pkgconfig.Config
+	ConfigChan     chan *pkgconfig.Config
+	name           string
+	chanSize       int
+	RoutingHandler pkgutils.RoutingHandler
 }
 
 func NewPdnsProcessor(connID int, config *pkgconfig.Config, logger *logger.Logger, name string, size int) PdnsProcessor {
 	logger.Info("[%s] processor=pdns#%d - initialization...", name, connID)
 	d := PdnsProcessor{
-		ConnID:       connID,
-		doneMonitor:  make(chan bool),
-		doneRun:      make(chan bool),
-		stopMonitor:  make(chan bool),
-		stopRun:      make(chan bool),
-		recvFrom:     make(chan []byte, size),
-		chanSize:     size,
-		logger:       logger,
-		config:       config,
-		ConfigChan:   make(chan *pkgconfig.Config),
-		name:         name,
-		dropped:      make(chan string),
-		droppedCount: map[string]int{},
+		ConnID:         connID,
+		doneMonitor:    make(chan bool),
+		doneRun:        make(chan bool),
+		stopMonitor:    make(chan bool),
+		stopRun:        make(chan bool),
+		recvFrom:       make(chan []byte, size),
+		chanSize:       size,
+		logger:         logger,
+		config:         config,
+		ConfigChan:     make(chan *pkgconfig.Config),
+		name:           name,
+		RoutingHandler: pkgutils.NewRoutingHandler(config, logger, name),
 	}
 	return d
 }
@@ -87,57 +86,23 @@ func (p *PdnsProcessor) GetChannel() chan []byte {
 }
 
 func (p *PdnsProcessor) Stop() {
+	p.LogInfo("stopping routing handler...")
+	p.RoutingHandler.Stop()
+
 	p.LogInfo("stopping to process...")
 	p.stopRun <- true
 	<-p.doneRun
-
-	p.LogInfo("stopping to monitor loggers...")
-	p.stopMonitor <- true
-	<-p.doneMonitor
 }
 
-func (p *PdnsProcessor) MonitorLoggers() {
-	watchInterval := 10 * time.Second
-	bufferFull := time.NewTimer(watchInterval)
-FOLLOW_LOOP:
-	for {
-		select {
-		case <-p.stopMonitor:
-			close(p.dropped)
-			bufferFull.Stop()
-			p.doneMonitor <- true
-			break FOLLOW_LOOP
-
-		case loggerName := <-p.dropped:
-			if _, ok := p.droppedCount[loggerName]; !ok {
-				p.droppedCount[loggerName] = 1
-			} else {
-				p.droppedCount[loggerName]++
-			}
-
-		case <-bufferFull.C:
-
-			for v, k := range p.droppedCount {
-				if k > 0 {
-					p.LogError("logger[%s] buffer is full, %d packet(s) dropped", v, k)
-					p.droppedCount[v] = 0
-				}
-			}
-			bufferFull.Reset(watchInterval)
-
-		}
-	}
-	p.LogInfo("monitor terminated")
-}
-
-func (p *PdnsProcessor) Run(loggersChannel []chan dnsutils.DNSMessage, loggersName []string) {
+func (p *PdnsProcessor) Run(defaultWorkers []pkgutils.Worker, droppedworkers []pkgutils.Worker) {
 	pbdm := &powerdns_protobuf.PBDNSMessage{}
 
-	// prepare enabled transformers
-	transforms := transformers.NewTransforms(&p.config.IngoingTransformers, p.logger, p.name, loggersChannel, p.ConnID)
+	// prepare next channels
+	defaultRoutes, defaultNames := pkgutils.GetRoutes(defaultWorkers)
+	droppedRoutes, droppedNames := pkgutils.GetRoutes(droppedworkers)
 
-	// start goroutine to count dropped messsages
-	go p.MonitorLoggers()
+	// prepare enabled transformers
+	transforms := transformers.NewTransforms(&p.config.IngoingTransformers, p.logger, p.name, defaultRoutes, p.ConnID)
 
 	// read incoming dns message
 	p.LogInfo("waiting dns message to process...")
@@ -354,17 +319,12 @@ RUN_LOOP:
 
 			// apply all enabled transformers
 			if transforms.ProcessMessage(&dm) == transformers.ReturnDrop {
+				p.RoutingHandler.SendTo(droppedRoutes, droppedNames, dm)
 				continue
 			}
 
 			// dispatch dns messages to connected loggers
-			for i := range loggersChannel {
-				select {
-				case loggersChannel[i] <- dm: // Successful send to logger channel
-				default:
-					p.dropped <- loggersName[i]
-				}
-			}
+			p.RoutingHandler.SendTo(defaultRoutes, defaultNames, dm)
 		}
 	}
 	p.LogInfo("processing terminated")
diff --git a/processors/powerdns_test.go b/processors/powerdns_test.go
index 9babc599..2c34bcda 100644
--- a/processors/powerdns_test.go
+++ b/processors/powerdns_test.go
@@ -3,8 +3,9 @@ package processors
 import (
 	"testing"
 
-	"github.com/dmachard/go-dnscollector/dnsutils"
+	"github.com/dmachard/go-dnscollector/loggers"
 	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-dnscollector/pkgutils"
 	"github.com/dmachard/go-logger"
 	powerdns_protobuf "github.com/dmachard/go-powerdns-protobuf"
 	"github.com/miekg/dns"
@@ -14,7 +15,6 @@ import (
 func TestPowerDNS_Processor(t *testing.T) {
 	// init the dnstap consumer
 	consumer := NewPdnsProcessor(0, pkgconfig.GetFakeConfig(), logger.New(false), "test", 512)
-	chanTo := make(chan dnsutils.DNSMessage, 512)
 
 	// init the powerdns processor
 	dnsQname := pkgconfig.ValidDomain
@@ -29,12 +29,15 @@ func TestPowerDNS_Processor(t *testing.T) {
 
 	data, _ := proto.Marshal(dm)
 
-	go consumer.Run([]chan dnsutils.DNSMessage{chanTo}, []string{"test"})
+	// run the consumer with a fake logger
+	fl := loggers.NewFakeLogger()
+	go consumer.Run([]pkgutils.Worker{fl}, []pkgutils.Worker{fl})
+
 	// add packet to consumer
 	consumer.GetChannel() <- data
 
 	// read dns message from dnstap consumer
-	msg := <-chanTo
+	msg := <-fl.GetInputChannel()
 	if msg.DNSTap.Identity != "powerdnspb" {
 		t.Errorf("invalid identity in dns message: %s", msg.DNSTap.Identity)
 	}
@@ -46,7 +49,6 @@ func TestPowerDNS_Processor_AddDNSPayload_Valid(t *testing.T) {
 
 	// init the powerdns processor
 	consumer := NewPdnsProcessor(0, cfg, logger.New(false), "test", 512)
-	chanTo := make(chan dnsutils.DNSMessage, 1)
 
 	// prepare powerdns message
 	dnsQname := pkgconfig.ValidDomain
@@ -63,11 +65,14 @@ func TestPowerDNS_Processor_AddDNSPayload_Valid(t *testing.T) {
 	data, _ := proto.Marshal(dm)
 
 	// start the consumer and add packet
-	go consumer.Run([]chan dnsutils.DNSMessage{chanTo}, []string{"test"})
+	// run the consumer with a fake logger
+	fl := loggers.NewFakeLogger()
+	go consumer.Run([]pkgutils.Worker{fl}, []pkgutils.Worker{fl})
+
 	consumer.GetChannel() <- data
 
 	// read dns message
-	msg := <-chanTo
+	msg := <-fl.GetInputChannel()
 
 	// checks
 	if msg.DNS.Length == 0 {
@@ -94,7 +99,6 @@ func TestPowerDNS_Processor_AddDNSPayload_InvalidLabelLength(t *testing.T) {
 
 	// init the dnstap consumer
 	consumer := NewPdnsProcessor(0, cfg, logger.New(false), "test", 512)
-	chanTo := make(chan dnsutils.DNSMessage, 512)
 
 	// prepare dnstap
 	dnsQname := pkgconfig.BadDomainLabel
@@ -110,12 +114,15 @@ func TestPowerDNS_Processor_AddDNSPayload_InvalidLabelLength(t *testing.T) {
 
 	data, _ := proto.Marshal(dm)
 
-	go consumer.Run([]chan dnsutils.DNSMessage{chanTo}, []string{"test"})
+	// run the consumer with a fake logger
+	fl := loggers.NewFakeLogger()
+	go consumer.Run([]pkgutils.Worker{fl}, []pkgutils.Worker{fl})
+
 	// add packet to consumer
 	consumer.GetChannel() <- data
 
 	// read dns message from dnstap consumer
-	msg := <-chanTo
+	msg := <-fl.GetInputChannel()
 	if !msg.DNS.MalformedPacket {
 		t.Errorf("DNS message should malformed")
 	}
@@ -127,7 +134,6 @@ func TestPowerDNS_Processor_AddDNSPayload_QnameTooLongDomain(t *testing.T) {
 
 	// init the dnstap consumer
 	consumer := NewPdnsProcessor(0, cfg, logger.New(false), "test", 512)
-	chanTo := make(chan dnsutils.DNSMessage, 512)
 
 	// prepare dnstap
 	dnsQname := pkgconfig.BadVeryLongDomain
@@ -142,12 +148,15 @@ func TestPowerDNS_Processor_AddDNSPayload_QnameTooLongDomain(t *testing.T) {
 
 	data, _ := proto.Marshal(dm)
 
-	go consumer.Run([]chan dnsutils.DNSMessage{chanTo}, []string{"test"})
+	// run the consumer with a fake logger
+	fl := loggers.NewFakeLogger()
+	go consumer.Run([]pkgutils.Worker{fl}, []pkgutils.Worker{fl})
+
 	// add packet to consumer
 	consumer.GetChannel() <- data
 
 	// read dns message from dnstap consumer
-	msg := <-chanTo
+	msg := <-fl.GetInputChannel()
 	if !msg.DNS.MalformedPacket {
 		t.Errorf("DNS message should malformed because of qname too long")
 	}
@@ -159,7 +168,6 @@ func TestPowerDNS_Processor_AddDNSPayload_AnswersTooLongDomain(t *testing.T) {
 
 	// init the dnstap consumer
 	consumer := NewPdnsProcessor(0, cfg, logger.New(false), "test", 512)
-	chanTo := make(chan dnsutils.DNSMessage, 512)
 
 	// prepare dnstap
 	dnsQname := pkgconfig.ValidDomain
@@ -185,12 +193,15 @@ func TestPowerDNS_Processor_AddDNSPayload_AnswersTooLongDomain(t *testing.T) {
 
 	data, _ := proto.Marshal(dm)
 
-	go consumer.Run([]chan dnsutils.DNSMessage{chanTo}, []string{"test"})
+	// run the consumer with a fake logger
+	fl := loggers.NewFakeLogger()
+	go consumer.Run([]pkgutils.Worker{fl}, []pkgutils.Worker{fl})
+
 	// add packet to consumer
 	consumer.GetChannel() <- data
 
 	// read dns message from dnstap consumer
-	msg := <-chanTo
+	msg := <-fl.GetInputChannel()
 
 	// tests verifications
 	if !msg.DNS.MalformedPacket {
diff --git a/transformers/atags.go b/transformers/atags.go
new file mode 100644
index 00000000..aff6e0b2
--- /dev/null
+++ b/transformers/atags.go
@@ -0,0 +1,57 @@
+package transformers
+
+import (
+	"github.com/dmachard/go-dnscollector/dnsutils"
+	"github.com/dmachard/go-dnscollector/pkgconfig"
+	"github.com/dmachard/go-logger"
+)
+
+type ATagsProcessor struct {
+	config      *pkgconfig.ConfigTransformers
+	logger      *logger.Logger
+	name        string
+	instance    int
+	outChannels []chan dnsutils.DNSMessage
+	logInfo     func(msg string, v ...interface{})
+	logError    func(msg string, v ...interface{})
+}
+
+func NewATagsSubprocessor(config *pkgconfig.ConfigTransformers, logger *logger.Logger, name string,
+	instance int, outChannels []chan dnsutils.DNSMessage,
+	logInfo func(msg string, v ...interface{}), logError func(msg string, v ...interface{})) ATagsProcessor {
+	s := ATagsProcessor{
+		config:      config,
+		logger:      logger,
+		name:        name,
+		instance:    instance,
+		outChannels: outChannels,
+		logInfo:     logInfo,
+		logError:    logError,
+	}
+
+	return s
+}
+
+func (p *ATagsProcessor) ReloadConfig(config *pkgconfig.ConfigTransformers) {
+	p.config = config
+}
+
+func (p *ATagsProcessor) InitDNSMessage(dm *dnsutils.DNSMessage) {
+	if dm.ATags == nil {
+		dm.ATags = &dnsutils.TransformATags{
+			Tags: []string{},
+		}
+
+	}
+}
+
+func (p *ATagsProcessor) IsEnabled() bool {
+	return p.config.ATags.Enable
+}
+
+func (p *ATagsProcessor) AddTags(dm *dnsutils.DNSMessage) int {
+	if p.config.ATags.Enable {
+		dm.ATags.Tags = append(dm.ATags.Tags, p.config.ATags.Tags...)
+	}
+	return ReturnSuccess
+}
diff --git a/transformers/subprocessors.go b/transformers/subprocessors.go
index 075b0604..71b5cd6b 100644
--- a/transformers/subprocessors.go
+++ b/transformers/subprocessors.go
@@ -33,6 +33,7 @@ type Transforms struct {
 	ReducerTransform         *ReducerProcessor
 	ExtractProcessor         ExtractProcessor
 	MachineLearningTransform MlProcessor
+	ATagsTransform           ATagsProcessor
 
 	activeTransforms []func(dm *dnsutils.DNSMessage) int
 }
@@ -55,6 +56,7 @@ func NewTransforms(config *pkgconfig.ConfigTransformers, logger *logger.Logger,
 	d.FilteringTransform = NewFilteringProcessor(config, logger, name, instance, outChannels, d.LogInfo, d.LogError)
 	d.GeoipTransform = NewDNSGeoIPProcessor(config, logger, name, instance, outChannels, d.LogInfo, d.LogError)
 	d.MachineLearningTransform = NewMachineLearningSubprocessor(config, logger, name, instance, outChannels, d.LogInfo, d.LogError)
+	d.ATagsTransform = NewATagsSubprocessor(config, logger, name, instance, outChannels, d.LogInfo, d.LogError)
 
 	d.Prepare()
 	return d
@@ -71,6 +73,7 @@ func (p *Transforms) ReloadConfig(config *pkgconfig.ConfigTransformers) {
 	p.ReducerTransform.ReloadConfig(config)
 	p.ExtractProcessor.ReloadConfig(config)
 	p.MachineLearningTransform.ReloadConfig(config)
+	p.ATagsTransform.ReloadConfig(config)
 
 	p.Prepare()
 }
@@ -169,6 +172,12 @@ func (p *Transforms) Prepare() error {
 		p.LogInfo(prefixlog + enabled)
 	}
 
+	if p.config.ATags.Enable {
+		p.activeTransforms = append(p.activeTransforms, p.ATagsTransform.AddTags)
+		prefixlog := fmt.Sprintf("transformer=atags#%d - ", p.instance)
+		p.LogInfo(prefixlog + "subprocessor atags is enabled")
+	}
+
 	return nil
 }
 
@@ -204,6 +213,10 @@ func (p *Transforms) InitDNSMessageFormat(dm *dnsutils.DNSMessage) {
 	if p.config.MachineLearning.Enable {
 		p.MachineLearningTransform.InitDNSMessage(dm)
 	}
+
+	if p.config.ATags.Enable {
+		p.ATagsTransform.InitDNSMessage(dm)
+	}
 }
 
 func (p *Transforms) Reset() {