Skip to content

Commit

Permalink
Filter by label (#236)
Browse files Browse the repository at this point in the history
* Filter by label

* Filter by label - fix with return statement

* Prefer strings.SplitN

* Ignore label filters if missing semicolon would cause panic
  • Loading branch information
davidnortonjr authored and mattatcha committed Dec 15, 2016
1 parent 977986a commit 105c59b
Show file tree
Hide file tree
Showing 6 changed files with 31 additions and 7 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,14 @@ You can tell logspout to only include certain containers by setting filter param
--volume=/var/run/docker.sock:/var/run/docker.sock \
gliderlabs/logspout \
raw://192.168.10.10:5000?filter.sources=stdout%2Cstderr

# Forward logs from containers with both label 'a' starting with 'x', and label 'b' ending in 'y'.
$ docker run \
--volume=/var/run/docker.sock:/var/run/docker.sock \
gliderlabs/logspout \
raw://192.168.10.10:5000?filter.labels=a:x*%2Cb:*y

Note that you must URL-encode parameter values such as the comma in `filter.sources`.
Note that you must URL-encode parameter values such as the comma in `filter.sources` and `filter.labels`.

#### Multiple logging destinations

Expand Down
2 changes: 1 addition & 1 deletion logspout.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func main() {
fmt.Fprintf(w, "# %s\t%s\t%s\t%s\t%s\n",
route.Adapter,
route.Address,
route.FilterID+route.FilterName,
route.FilterID+route.FilterName+strings.Join(route.FilterLabels, ","),
strings.Join(route.FilterSources, ","),
route.Options)
}
Expand Down
6 changes: 4 additions & 2 deletions router/pump.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,8 @@ func (p *LogsPump) Route(route *Route, logstream chan *Message) {
for _, pump := range p.pumps {
if route.MatchContainer(
normalID(pump.container.ID),
normalName(pump.container.Name)) {
normalName(pump.container.Name),
pump.container.Config.Labels) {

pump.add(logstream, route)
defer pump.remove(logstream)
Expand All @@ -280,7 +281,8 @@ func (p *LogsPump) Route(route *Route, logstream chan *Message) {
case "start", "restart":
if route.MatchContainer(
normalID(event.pump.container.ID),
normalName(event.pump.container.Name)) {
normalName(event.pump.container.Name),
event.pump.container.Config.Labels) {

event.pump.add(logstream, route)
defer event.pump.remove(logstream)
Expand Down
2 changes: 2 additions & 0 deletions router/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ func (rm *RouteManager) AddFromUri(uri string) error {
r.FilterID = value
case "filter.name":
r.FilterName = value
case "filter.labels":
r.FilterLabels = strings.Split(value, ",")
case "filter.sources":
r.FilterSources = strings.Split(value, ",")
default:
Expand Down
17 changes: 15 additions & 2 deletions router/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type Route struct {
FilterID string `json:"filter_id,omitempty"`
FilterName string `json:"filter_name,omitempty"`
FilterSources []string `json:"filter_sources,omitempty"`
FilterLabels []string `json:"filter_labels,omitempty"`
Adapter string `json:"adapter"`
Address string `json:"address"`
Options map[string]string `json:"options,omitempty"`
Expand Down Expand Up @@ -97,7 +98,7 @@ func (r *Route) Close() {
}

func (r *Route) matchAll() bool {
if r.FilterID == "" && r.FilterName == "" && len(r.FilterSources) == 0 {
if r.FilterID == "" && r.FilterName == "" && len(r.FilterSources) == 0 && len(r.FilterLabels) == 0 {
return true
}
return false
Expand All @@ -107,7 +108,7 @@ func (r *Route) MultiContainer() bool {
return r.matchAll() || strings.Contains(r.FilterName, "*")
}

func (r *Route) MatchContainer(id, name string) bool {
func (r *Route) MatchContainer(id, name string, labels map[string]string) bool {
if r.matchAll() {
return true
}
Expand All @@ -118,6 +119,18 @@ func (r *Route) MatchContainer(id, name string) bool {
if err != nil || (r.FilterName != "" && !match) {
return false
}
for _,label := range r.FilterLabels {
labelParts := strings.SplitN(label, ":", 2)
if len(labelParts) > 1 {
labelKey := labelParts[0]
labelValue := labelParts[1]
labelMatch, labelErr := path.Match(labelValue, labels[labelKey])
if labelErr != nil || (labelValue != "" && !labelMatch) {
return false
}
}
}

return true
}

Expand Down
3 changes: 2 additions & 1 deletion routesapi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ Takes a JSON object like this:
"address": "logaggregator.service.consul",
"filter_name": "*_db",
"filter_sources": ["stdout"],
"filter_labels": ["com.example.foo:bar*"],
"options": {
"append_tag": ".db"
}
}

The main fields are `adapter` and `address`. The field `options` is passed to the adapter. There are three filter fields: `filter_name`, `filter_sources`, and `filter_id`. These let you limit which containers or types of logs to route. Use `filter_id` to limit to a particular container by ID. Use `filter_name` to match against container names. These can include wildcards. Use `filter_sources` to limit to `stdout` or `stderr`, or soon `syslog`.
The main fields are `adapter` and `address`. The field `options` is passed to the adapter. There are four filter fields: `filter_name`, `filter_sources`, `filter_id`, and `filter_labels`. These let you limit which containers or types of logs to route. Use `filter_id` to limit to a particular container by ID. Use `filter_name` to match against container names. These can include wildcards. Use `filter_sources` to limit to `stdout` or `stderr`, or soon `syslog`. Use `filter_labels` to limit containers to require specific labels. These can include wildcards.

To route all logs of all types on all containers, don't specify any filter values.

Expand Down

0 comments on commit 105c59b

Please sign in to comment.