diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000000000000000000000000000000000000..63c6cfc8a5da15498d63815b721ee09beca0eab6
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,32 @@
+name: build
+
+on:
+  push:
+    branches: [ master ]
+  pull_request:
+    branches: [ master ]
+
+jobs:
+
+  build:
+    name: Build
+    runs-on: ubuntu-latest
+    steps:
+
+    - name: Set up Go 1.13
+      uses: actions/setup-go@v1
+      with:
+        go-version: 1.13
+      id: go
+
+    - name: Check out code into the Go module directory
+      uses: actions/checkout@v2
+
+    - name: Get dependencies
+      run: go get -v -t -d ./...
+
+    - name: Build
+      run: go build -v .
+
+    - name: Test
+      run: go test -v ./...
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2219910ae91aacb31f8f9fb9e851495e1f5731f5
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,26 @@
+name: release
+
+on:
+  release:
+    types:
+      - created
+
+jobs:
+  release:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+      - name: Unshallow
+        run: git fetch --prune --unshallow
+      - name: Set up Go
+        uses: actions/setup-go@v1
+        with:
+          go-version: 1.13.x
+      - name: Run GoReleaser
+        uses: goreleaser/goreleaser-action@v1
+        with:
+          version: latest
+          args: release --rm-dist
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 9e60eef838ee79cd6ce03d70ae7e461d1ee9864a..7f0e8dd45f2ae60c5dea4970343fdc20262daf01 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,3 +23,5 @@ _testmain.go
 *.test
 *.prof
 rsyslog_exporter
+
+dist
diff --git a/.goreleaser.yml b/.goreleaser.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d87e2a279ab179eb723486f89d71c6f1bf79862f
--- /dev/null
+++ b/.goreleaser.yml
@@ -0,0 +1,46 @@
+env:
+  - GO111MODULE=on
+  - GOPROXY=https://gocenter.io
+before:
+  hooks:
+    - go mod download
+builds:
+  - env:
+      - CGO_ENABLED=0
+    goos:
+      - linux
+    goarch:
+      - amd64
+      - arm
+      - arm64
+checksum:
+  name_template: "{{ .ProjectName }}_{{ .Version }}_checksums.txt"
+changelog:
+  sort: asc
+  filters:
+    exclude:
+      - "^docs:"
+      - "^test:"
+      - Merge pull request
+      - Merge branch
+archives:
+  - name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
+    replacements:
+      linux: Linux
+      amd64: x86_64
+nfpms:
+  - package_name: "rsyslog-exporter"
+    homepage: https://github.com/aleroyer/rsyslog_exporter
+    description: rsyslog-exporter for prometheus
+    maintainer: Antoine Leroyer <aleroyer@deezer.com>
+    license: Apache 2.0
+    bindir: /usr/bin
+    release: 1
+    formats:
+      - deb
+      - rpm
+    overrides:
+      deb:
+        file_name_template: '{{ replace .ProjectName "_" "-" }}_{{ .Version }}-{{ .Release }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
+      rpm:
+        file_name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Release }}.{{ .Arch }}"
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index c095a514d787a3059cb334f3219fda1bd1d22bd4..0000000000000000000000000000000000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-language: go
-
-go:
-    - 1.4
-
-install:
-    - go get github.com/digitalocean/rsyslog_exporter
-
-script:
-    - make test
diff --git a/README.md b/README.md
index 7a7d5863c040742be5c0f28eb5d73f88fd388ace..73357f3cd75ca3d993fa5616a444157aeb2e40c2 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,8 @@ A [prometheus](http://prometheus.io/) exporter for [rsyslog](http://rsyslog.com)
 ## Rsyslog Configuration
 Configure rsyslog to push JSON formatted stats via omprog:
 ```
+module(load="omprog")
+
 module(
   load="impstats"
   interval="10"
@@ -17,15 +19,27 @@ ruleset(name="process_stats") {
   action(
     type="omprog"
     name="to_exporter"
-    binary="/usr/local/bin/rsyslog_exporter"
+    binary="/usr/local/bin/rsyslog_exporter [--tls.server-crt=/path/to/tls.crt --tls.server-key=/path/to/tls.key]"
   )
 }
 ```
 
 The exporter itself logs back via syslog, this cannot be configured at the moment.
 
+## Command Line Switches
+* `web.listen-address` - default `:9104` - port to listen to (NOTE: the leading
+  `:` is required for `http.ListenAndServe`)
+* `web.telemetry-path` - default `/metrics` - path from which to serve Prometheus metrics
+* `tls.server-crt` - default `""` - PEM encoded file containing the server certificate and
+  the CA certificate for use with `http.ListenAndServeTLS`
+* `tls.server-key` - default `""` - PEM encoded file containing the unencrypted
+  server key for use with `tls.server-crt`
+
+If you want the exporter to listen for TLS (`https`) you must specify both
+`tls.server-crt` and `tls.server-key`.
+
 ## Provided Metrics
-The following metrics provided by the rsyslog impstats module are tracked by rsyslog_exporter:
+The following metrics provided by the rsyslog [impstats](https://www.rsyslog.com/doc/master/configuration/modules/impstats.html) module are tracked by rsyslog_exporter:
 
 ### Actions
 Action objects describe what is to be done with a message, and are implemented via output modules.
@@ -68,4 +82,33 @@ Rsyslog tracks how it uses system resources and provides the following metrics:
 * nvcsw - number of voluntary context switches
 * nivcsw - number of involuntary context switches
 
+### Dynafile Cache
+The [omfile](https://www.rsyslog.com/rsyslog-statistic-counter-plugin-omfile/) module can generate
+file names from a template.  A cache of recent filehandles can be maintained, whose sizing can
+impact performance considerably.  The module provides the following metrics:
+
+* requests - number of requests made to obtain a dynafile
+* level0 - number of requests for the current active file
+* missed - number of cache misses
+* evicted - number of times a file needed to be evicted from cache
+* maxused - maximum number of cache entries ever used
+* closetimeouts - number of times a file was closed due to timeout settings
+
+### Dynamic Stats
+Rsyslog allows the user to define their own stats namespaces and increment counters within these
+buckets using Rainerscript function calls.
+
+These are exported as counters with the metric name identifying the bucket, and a label value
+matching the name of the counter (the label name will always be "counter").  As well as custom
+metrics, a "global" dynstats namespace is also published with some additional bookeeping counters.
+
+See the [dyn_stats](https://www.rsyslog.com/doc/master/configuration/dyn_stats.html)
+documentation for more information.
+
+### IMUDP Workerthread stats
+The [imudp](https://www.rsyslog.com/rsyslog-statistic-counter-plugin-imudp/) module can be configured
+to run on multiple worker threads and the following metrics are returned:
 
+* input_called_recvmmsg - Number of recvmmsg called
+* input_called_recvmsg -Number of recvmmsg called
+* input_received - Messages received
diff --git a/dyn_stat_test.go b/dyn_stat_test.go
index 8d7ff843aeab873032c718105b8a4725a2bfbebf..d96bea5255da2b907196a9297e15b55a3eb24ca4 100644
--- a/dyn_stat_test.go
+++ b/dyn_stat_test.go
@@ -79,7 +79,7 @@ func TestDynStatToPoints(t *testing.T) {
 	}
 
 	seen := map[string]bool{}
-	for name, _ := range wants {
+	for name := range wants {
 		seen[name] = false
 	}
 
diff --git a/dynafile_cache.go b/dynafile_cache.go
new file mode 100644
index 0000000000000000000000000000000000000000..f0e1f25837b697c1c899845e81ca56299221a6df
--- /dev/null
+++ b/dynafile_cache.go
@@ -0,0 +1,83 @@
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"strings"
+)
+
+type dfcStat struct {
+	Name          string `json:"name"`
+	Origin        string `json:"origin"`
+	Requests      int64  `json:"requests"`
+	Level0        int64  `json:"level0"`
+	Missed        int64  `json:"missed"`
+	Evicted       int64  `json:"evicted"`
+	MaxUsed       int64  `json:"maxused"`
+	CloseTimeouts int64  `json:"closetimeouts"`
+}
+
+func newDynafileCacheFromJSON(b []byte) (*dfcStat, error) {
+	var pstat dfcStat
+	err := json.Unmarshal(b, &pstat)
+	if err != nil {
+		return nil, fmt.Errorf("error decoding dynafile cache stat `%v`: %v", string(b), err)
+	}
+	pstat.Name = strings.TrimPrefix(pstat.Name, "dynafile cache ")
+	return &pstat, nil
+}
+
+func (d *dfcStat) toPoints() []*point {
+	points := make([]*point, 6)
+
+	points[0] = &point{
+		Name:        "dynafile_cache_requests",
+		Type:        counter,
+		Value:       d.Requests,
+		Description: "number of requests made to obtain a dynafile",
+		LabelName:   "cache",
+		LabelValue:  d.Name,
+	}
+	points[1] = &point{
+		Name:        "dynafile_cache_level0",
+		Type:        counter,
+		Value:       d.Level0,
+		Description: "number of requests for the current active file",
+		LabelName:   "cache",
+		LabelValue:  d.Name,
+	}
+	points[2] = &point{
+		Name:        "dynafile_cache_missed",
+		Type:        counter,
+		Value:       d.Missed,
+		Description: "number of cache misses",
+		LabelName:   "cache",
+		LabelValue:  d.Name,
+	}
+	points[3] = &point{
+		Name:        "dynafile_cache_evicted",
+		Type:        counter,
+		Value:       d.Evicted,
+		Description: "number of times a file needed to be evicted from cache",
+		LabelName:   "cache",
+		LabelValue:  d.Name,
+	}
+	points[4] = &point{
+		Name:        "dynafile_cache_maxused",
+		Type:        counter,
+		Value:       d.MaxUsed,
+		Description: "maximum number of cache entries ever used",
+		LabelName:   "cache",
+		LabelValue:  d.Name,
+	}
+	points[5] = &point{
+		Name:        "dynafile_cache_closetimeouts",
+		Type:        counter,
+		Value:       d.CloseTimeouts,
+		Description: "number of times a file was closed due to timeout settings",
+		LabelName:   "cache",
+		LabelValue:  d.Name,
+	}
+
+	return points
+}
diff --git a/dynafile_cache_test.go b/dynafile_cache_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..a7cd762ac90e53a27e0f60176742a0f075856a6f
--- /dev/null
+++ b/dynafile_cache_test.go
@@ -0,0 +1,139 @@
+package main
+
+import (
+	"reflect"
+	"testing"
+)
+
+var (
+	dynafileCacheLog = []byte(`{ "name": "dynafile cache cluster", "origin": "omfile", "requests": 1783254, "level0": 1470906, "missed": 2625, "evicted": 2525, "maxused": 100, "closetimeouts": 10 }`)
+)
+
+func TestNewDynafileCacheFromJSON(t *testing.T) {
+	logType := getStatType(dynafileCacheLog)
+	if logType != rsyslogDynafileCache {
+		t.Errorf("detected pstat type should be %d but is %d", rsyslogDynafileCache, logType)
+	}
+
+	pstat, err := newDynafileCacheFromJSON([]byte(dynafileCacheLog))
+	if err != nil {
+		t.Fatalf("expected parsing dynafile cache stat not to fail, got: %v", err)
+	}
+
+	if want, got := "cluster", pstat.Name; want != got {
+		t.Errorf("want '%s', got '%s'", want, got)
+	}
+
+	if want, got := int64(1783254), pstat.Requests; want != got {
+		t.Errorf("want '%d', got '%d'", want, got)
+	}
+
+	if want, got := int64(1470906), pstat.Level0; want != got {
+		t.Errorf("want '%d', got '%d'", want, got)
+	}
+
+	if want, got := int64(2625), pstat.Missed; want != got {
+		t.Errorf("want '%d', got '%d'", want, got)
+	}
+
+	if want, got := int64(2525), pstat.Evicted; want != got {
+		t.Errorf("want '%d', got '%d'", want, got)
+	}
+
+	if want, got := int64(100), pstat.MaxUsed; want != got {
+		t.Errorf("want '%d', got '%d'", want, got)
+	}
+
+	if want, got := int64(10), pstat.CloseTimeouts; want != got {
+		t.Errorf("want '%d', got '%d'", want, got)
+	}
+}
+
+func TestDynafileCacheToPoints(t *testing.T) {
+
+	wants := map[string]point{
+		"dynafile_cache_requests": point{
+			Name:        "dynafile_cache_requests",
+			Type:        counter,
+			Value:       1783254,
+			Description: "number of requests made to obtain a dynafile",
+			LabelName:   "cache",
+			LabelValue:  "cluster",
+		},
+		"dynafile_cache_level0": point{
+			Name:        "dynafile_cache_level0",
+			Type:        counter,
+			Value:       1470906,
+			Description: "number of requests for the current active file",
+			LabelName:   "cache",
+
+			LabelValue: "cluster",
+		},
+		"dynafile_cache_missed": point{
+			Name:        "dynafile_cache_missed",
+			Type:        counter,
+			Value:       2625,
+			Description: "number of cache misses",
+			LabelName:   "cache",
+			LabelValue:  "cluster",
+		},
+		"dynafile_cache_evicted": point{
+			Name:        "dynafile_cache_evicted",
+			Type:        counter,
+			Value:       2525,
+			Description: "number of times a file needed to be evicted from cache",
+			LabelName:   "cache",
+			LabelValue:  "cluster",
+		},
+		"dynafile_cache_maxused": point{
+			Name:        "dynafile_cache_maxused",
+			Type:        counter,
+			Value:       100,
+			Description: "maximum number of cache entries ever used",
+			LabelName:   "cache",
+			LabelValue:  "cluster",
+		},
+		"dynafile_cache_closetimeouts": point{
+			Name:        "dynafile_cache_closetimeouts",
+			Type:        counter,
+			Value:       10,
+			Description: "number of times a file was closed due to timeout settings",
+			LabelName:   "cache",
+			LabelValue:  "cluster",
+		},
+	}
+
+	seen := map[string]bool{}
+	for name := range wants {
+		seen[name] = false
+	}
+
+	pstat, err := newDynafileCacheFromJSON(dynafileCacheLog)
+	if err != nil {
+		t.Fatalf("expected parsing dynafile cache stat not to fail, got: %v", err)
+	}
+
+	points := pstat.toPoints()
+	for _, got := range points {
+		want, ok := wants[got.Name]
+		if !ok {
+			t.Errorf("unexpected point, got: %+v", got)
+			continue
+		}
+
+		if !reflect.DeepEqual(want, *got) {
+			t.Errorf("expected point to be %+v, got %+v", want, got)
+		}
+
+		if seen[got.Name] {
+			t.Errorf("point seen multiple times: %+v", got)
+		}
+		seen[got.Name] = true
+	}
+
+	for name, ok := range seen {
+		if !ok {
+			t.Errorf("expected to see point with key %s, but did not", name)
+		}
+	}
+}
diff --git a/exporter.go b/exporter.go
index 746c8c65580f4b1f4e0ad06ea673503b8a49e513..5ee725c340abf07ed548ddd485b66d81e74d6be9 100644
--- a/exporter.go
+++ b/exporter.go
@@ -20,6 +20,8 @@ const (
 	rsyslogQueue
 	rsyslogResource
 	rsyslogDynStat
+	rsyslogDynafileCache
+	rsyslogInputIMDUP
 )
 
 type rsyslogExporter struct {
@@ -68,6 +70,15 @@ func (re *rsyslogExporter) handleStatLine(rawbuf []byte) error {
 			re.set(p)
 		}
 
+	case rsyslogInputIMDUP:
+		u, err := newInputIMUDPFromJSON(buf)
+		if err != nil {
+			return err
+		}
+		for _, p := range u.toPoints() {
+			re.set(p)
+		}
+
 	case rsyslogQueue:
 		q, err := newQueueFromJSON(buf)
 		if err != nil {
@@ -93,6 +104,14 @@ func (re *rsyslogExporter) handleStatLine(rawbuf []byte) error {
 		for _, p := range s.toPoints() {
 			re.set(p)
 		}
+	case rsyslogDynafileCache:
+		d, err := newDynafileCacheFromJSON(buf)
+		if err != nil {
+			return err
+		}
+		for _, p := range d.toPoints() {
+			re.set(p)
+		}
 
 	default:
 		return fmt.Errorf("unknown pstat type: %v", pstatType)
diff --git a/exporter_test.go b/exporter_test.go
index df2326e6102bbf541aa8c9afc389793df6b4d7ab..0afded362deec3e6b39c1eaa0fc882efed47a0e6 100644
--- a/exporter_test.go
+++ b/exporter_test.go
@@ -230,6 +230,34 @@ func TestHandleLineWithGlobal(t *testing.T) {
 	testHelper(t, log, tests)
 }
 
+func TestHandleLineWithDynafileCache(t *testing.T) {
+	tests := []*testUnit{
+		&testUnit{
+			Name:       "dynafile_cache_requests",
+			Val:        412044,
+			LabelValue: "cluster",
+		},
+		&testUnit{
+			Name:       "dynafile_cache_level0",
+			Val:        294002,
+			LabelValue: "cluster",
+		},
+		&testUnit{
+			Name:       "dynafile_cache_missed",
+			Val:        210,
+			LabelValue: "cluster",
+		},
+		&testUnit{
+			Name:       "dynafile_cache_evicted",
+			Val:        14,
+			LabelValue: "cluster",
+		},
+	}
+
+	dynafileCacheLog := []byte(`2019-07-03T17:04:01.312432+00:00 some-node.example.org rsyslogd-pstats: { "name": "dynafile cache cluster", "origin": "omfile", "requests": 412044, "level0": 294002, "missed": 210, "evicted": 14, "maxused": 100, "closetimeouts": 0 }`)
+	testHelper(t, dynafileCacheLog, tests)
+}
+
 func TestHandleUnknown(t *testing.T) {
 	unknownLog := []byte(`2017-08-30T08:10:04.786350+00:00 some-node.example.org rsyslogd-pstats: {"a":"b"}`)
 
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000000000000000000000000000000000000..60075859a0a7ff0b589de13d205102004c349846
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,5 @@
+module github.com/aleroyer/rsyslog_exporter
+
+go 1.13
+
+require github.com/prometheus/client_golang v1.1.0
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000000000000000000000000000000000000..b8fb9d079d3f8a2b5aae1899d86e92d111983283
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,67 @@
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8=
+github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo=
+github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE=
+github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0=
+golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/input_imudp.go b/input_imudp.go
new file mode 100644
index 0000000000000000000000000000000000000000..13b5ec0a3eeb3c33aa941ef80822764b33ce6e78
--- /dev/null
+++ b/input_imudp.go
@@ -0,0 +1,54 @@
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+)
+
+type inputIMUDP struct {
+	Name     string `json:"name"`
+	Recvmmsg int64  `json:"called.recvmmsg"`
+	Recvmsg  int64  `json:"called.recvmsg"`
+	Received int64  `json:"msgs.received"`
+}
+
+func newInputIMUDPFromJSON(b []byte) (*inputIMUDP, error) {
+	var pstat inputIMUDP
+	err := json.Unmarshal(b, &pstat)
+	if err != nil {
+		return nil, fmt.Errorf("error decoding input stat `%v`: %v", string(b), err)
+	}
+	return &pstat, nil
+}
+
+func (i *inputIMUDP) toPoints() []*point {
+	points := make([]*point, 3)
+
+	points[0] = &point{
+		Name:        "input_called_recvmmsg",
+		Type:        counter,
+		Value:       i.Recvmmsg,
+		Description: "Number of recvmmsg called",
+		LabelName:   "worker",
+		LabelValue:  i.Name,
+	}
+	points[1] = &point{
+		Name:        "input_called_recvmsg",
+		Type:        counter,
+		Value:       i.Recvmsg,
+		Description: "Number of recvmmsg called",
+		LabelName:   "worker",
+		LabelValue:  i.Name,
+	}
+
+	points[2] = &point{
+		Name:        "input_received",
+		Type:        counter,
+		Value:       i.Received,
+		Description: "messages received",
+		LabelName:   "worker",
+		LabelValue:  i.Name,
+	}
+
+	return points
+}
diff --git a/input_imudp_test.go b/input_imudp_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..8c98b6301a60cb459ca8142ac83d2d58139afd06
--- /dev/null
+++ b/input_imudp_test.go
@@ -0,0 +1,83 @@
+package main
+
+import "testing"
+
+var (
+	inputIMUDPLog = []byte(`{ "name": "test_input_imudp", "origin": "imudp", "called.recvmmsg":1000, "called.recvmsg":2000, "msgs.received":500}`)
+)
+
+func TestgetInputIMUDP(t *testing.T) {
+	logType := getStatType(inputIMUDPLog)
+	if logType != rsyslogInputIMDUP {
+		t.Errorf("detected pstat type should be %d but is %d", rsyslogInputIMDUP, logType)
+	}
+
+	pstat, err := newInputIMUDPFromJSON([]byte(inputLog))
+	if err != nil {
+		t.Fatalf("expected parsing input stat not to fail, got: %v", err)
+	}
+
+	if want, got := "test_input_imudp", pstat.Name; want != got {
+		t.Errorf("want '%s', got '%s'", want, got)
+	}
+
+	if want, got := int64(1000), pstat.Recvmsg; want != got {
+		t.Errorf("want '%d', got '%d'", want, got)
+	}
+
+	if want, got := int64(2000), pstat.Recvmmsg; want != got {
+		t.Errorf("want '%d', got '%d'", want, got)
+	}
+
+	if want, got := int64(500), pstat.Received; want != got {
+		t.Errorf("want '%d', got '%d'", want, got)
+	}
+}
+
+func TestInputIMUDPtoPoints(t *testing.T) {
+	pstat, err := newInputIMUDPFromJSON([]byte(inputIMUDPLog))
+	if err != nil {
+		t.Fatalf("expected parsing input stat not to fail, got: %v", err)
+	}
+
+	points := pstat.toPoints()
+
+	point := points[0]
+	if want, got := "input_called_recvmmsg", point.Name; want != got {
+		t.Errorf("want '%s', got '%s'", want, got)
+	}
+
+	if want, got := int64(1000), point.Value; want != got {
+		t.Errorf("want '%d', got '%d'", want, got)
+	}
+
+	if want, got := "test_input_imudp", point.LabelValue; want != got {
+		t.Errorf("wanted '%s', got '%s'", want, got)
+	}
+
+	point = points[1]
+	if want, got := "input_called_recvmsg", point.Name; want != got {
+		t.Errorf("want '%s', got '%s'", want, got)
+	}
+
+	if want, got := int64(2000), point.Value; want != got {
+		t.Errorf("want '%d', got '%d'", want, got)
+	}
+
+	if want, got := "test_input_imudp", point.LabelValue; want != got {
+		t.Errorf("wanted '%s', got '%s'", want, got)
+	}
+
+	point = points[2]
+	if want, got := "input_received", point.Name; want != got {
+		t.Errorf("want '%s', got '%s'", want, got)
+	}
+
+	if want, got := int64(500), point.Value; want != got {
+		t.Errorf("want '%d', got '%d'", want, got)
+	}
+
+	if want, got := "test_input_imudp", point.LabelValue; want != got {
+		t.Errorf("wanted '%s', got '%s'", want, got)
+	}
+}
diff --git a/main.go b/main.go
index 029f26aec1dbeafb1c7e3dd07ff040a4f485d72d..90ef19f31fc95a93f21a1ba16fcd5ab4a03e9598 100644
--- a/main.go
+++ b/main.go
@@ -9,11 +9,14 @@ import (
 	"os/signal"
 
 	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/promhttp"
 )
 
 var (
 	listenAddress = flag.String("web.listen-address", ":9104", "Address to listen on for web interface and telemetry.")
 	metricPath    = flag.String("web.telemetry-path", "/metrics", "Path under which to expose metrics.")
+	certPath      = flag.String("tls.server-crt", "", "Path to PEM encoded file containing TLS server cert.")
+	keyPath       = flag.String("tls.server-key", "", "Path to PEM encoded file containing TLS server key (unencyrpted).")
 )
 
 func main() {
@@ -38,7 +41,7 @@ func main() {
 	}()
 
 	prometheus.MustRegister(exporter)
-	http.Handle(*metricPath, prometheus.Handler())
+	http.Handle(*metricPath, promhttp.Handler())
 	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
 		w.Write([]byte(`<html>
 <head><title>Rsyslog exporter</title></head>
@@ -50,6 +53,13 @@ func main() {
 `))
 	})
 
-	log.Printf("Listening on %s", *listenAddress)
-	log.Fatal(http.ListenAndServe(*listenAddress, nil))
+	if *certPath == "" && *keyPath == "" {
+		log.Printf("Listening on %s", *listenAddress)
+		log.Fatal(http.ListenAndServe(*listenAddress, nil))
+	} else if *certPath == "" || *keyPath == "" {
+		log.Fatal("Both tls.server-crt and tls.server-key must be specified")
+	} else {
+		log.Printf("Listening for TLS on %s", *listenAddress)
+		log.Fatal(http.ListenAndServeTLS(*listenAddress, *certPath, *keyPath, nil))
+	}
 }
diff --git a/point.go b/point.go
index 499cfc2788014f1b85fcf4d166fbcc56e41c7f89..1f441182587186f24017591c90c506bc963fb67b 100644
--- a/point.go
+++ b/point.go
@@ -1,7 +1,6 @@
 package main
 
 import (
-	"errors"
 	"fmt"
 
 	"github.com/prometheus/client_golang/prometheus"
@@ -14,11 +13,6 @@ const (
 	gauge
 )
 
-var (
-	ErrIncompatiblePointType = errors.New("incompatible point type")
-	ErrUnknownPointType      = errors.New("unknown point type")
-)
-
 type point struct {
 	Name        string
 	Description string
diff --git a/pointstore.go b/pointstore.go
index 3da93dfeb75e5cff6318706c4b10a78677ce57c1..632a46bb10945c17cb49c3019c12307f1161c523 100644
--- a/pointstore.go
+++ b/pointstore.go
@@ -7,7 +7,7 @@ import (
 )
 
 var (
-	ErrPointNotFound = errors.New("point does not exist")
+	errPointNotFound = errors.New("point does not exist")
 )
 
 type pointStore struct {
@@ -25,7 +25,7 @@ func newPointStore() *pointStore {
 func (ps *pointStore) keys() []string {
 	ps.lock.Lock()
 	keys := make([]string, 0)
-	for k, _ := range ps.pointMap {
+	for k := range ps.pointMap {
 		keys = append(keys, k)
 	}
 	sort.Strings(keys)
@@ -48,5 +48,5 @@ func (ps *pointStore) get(name string) (*point, error) {
 		return p, nil
 	}
 	ps.lock.Unlock()
-	return &point{}, ErrPointNotFound
+	return &point{}, errPointNotFound
 }
diff --git a/pointstore_test.go b/pointstore_test.go
index ad7afcbff5807eebc69bbeda6d8dbde3cbafba3e..39f09190f56c5d6e77421ca38face5fe7a1ed55f 100644
--- a/pointstore_test.go
+++ b/pointstore_test.go
@@ -86,7 +86,7 @@ func TestPointStore(t *testing.T) {
 	}
 
 	_, err = ps.get("no point")
-	if err != ErrPointNotFound {
+	if err != errPointNotFound {
 		t.Error("getting non existent point should raise error")
 	}
 }
diff --git a/utils.go b/utils.go
index 5130060c62c30bb4a93942959a7bf15bc1792270..1cd38e8fca5ed31a470412ce896505c99d4dbf70 100644
--- a/utils.go
+++ b/utils.go
@@ -8,12 +8,16 @@ func getStatType(buf []byte) rsyslogType {
 		return rsyslogAction
 	} else if strings.Contains(line, "submitted") {
 		return rsyslogInput
+	} else if strings.Contains(line, "called.recvmmsg") {
+		return rsyslogInputIMDUP
 	} else if strings.Contains(line, "enqueued") {
 		return rsyslogQueue
 	} else if strings.Contains(line, "utime") {
 		return rsyslogResource
 	} else if strings.Contains(line, "dynstats") {
 		return rsyslogDynStat
+	} else if strings.Contains(line, "dynafile cache") {
+		return rsyslogDynafileCache
 	}
 	return rsyslogUnknown
 }