Skip to content

Commit 6f3ae9f

Browse files
authored
Merge pull request #9 from akkeris/reject-messages
send reject messages to logshuttle
2 parents ea164cb + b9f4daa commit 6f3ae9f

File tree

4 files changed

+148
-16
lines changed

4 files changed

+148
-16
lines changed

Diff for: .dockerignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*env*
2+
*.git*

Diff for: .gitignore

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
maru.sh
2-
send.sh
3-
*.env
1+
*.env
2+
*.sh

Diff for: README.md

+24-8
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,32 @@ Examples:
2121

2222
## Requirements
2323

24-
Metrics Syslog Collector has two major requirements - a Postgres database and an OpenTSDB compatible server (i.e. influxdb)
24+
Metrics Syslog Collector has one required dependency and one optional - an OpenTSDB compatible server (i.e. influxdb) is required, and a Postgres database is optional (although required for metric limiting)
2525

2626
## Environment Variables
2727

28-
| Name | Description | Required |
29-
| --------------------- | ----------------------------------------------------------------- | --------- |
30-
| DATABASE_URL | URL connection string for a Postgres database | Required |
31-
| DEBUG | Set to "true" to print out additional degugging info | Optional |
32-
| OPENTSDB_IP | IP address of the OpenTSDB compatible server to send metrics to | Required |
33-
| PORT | Network port to listen on | Required |
34-
| UNIQUE_METRIC_LIMIT | How many metrics should be allowed per app - default is 100 | Optional |
28+
For basic usage, only the following environment variables are required:
29+
30+
| Name | Description | Required |
31+
| --------------------------- | ------------------------------------------------------------------------- | --------- |
32+
| OPENTSDB_IP | IP address of the OpenTSDB compatible server to send metrics to | Required |
33+
| PORT | Network port to listen on | Required |
34+
| DEBUG | Set to "true" to print out additional degugging info | Optional |
35+
36+
### Metric Limiting
37+
38+
If you wish to limit the number of unique metrics that apps can submit to Influx, you can use the following environment variables.
39+
40+
**NOTE:** A Postgres database is required for metric limiting
41+
42+
| Name | Description |
43+
| --------------------------- | ----------------------------------------------------------------------------------------------- |
44+
| ENABLE_UNIQUE_METRIC_LIMIT | Limit how many metrics each app can send to influx |
45+
| DATABASE_URL | URL connection string for a Postgres database. **Required** |
46+
| UNIQUE_METRIC_LIMIT | How many metrics should be allowed per app? (default 100) |
47+
| LOGSHUTTLE_URL | Set this to "true" to send rejection messages back to the app's logs |
48+
| UNIQUE_METRIC_LIMIT_HELP | Specify a link to the docs in the rejection message (optional) |
49+
| REJECT_MESSAGE_LIMIT | Limit the number of times we report to the app logs (default 1) (reset on application restart) |
3550

3651
## Running
3752

@@ -92,6 +107,7 @@ $ createdb metrics-syslog-collector
92107

93108
Get your environment variables ready and put them in a file (`config.env`):
94109
```
110+
export ENABLE_UNIQUE_METRIC_LIMIT=true
95111
export DATABASE_URL=postgres://[YOUR USER NAME]@127.0.0.1:5432/metrics-syslog-collector?sslmode=disable
96112
export DEBUG=true
97113
export OPENTSDB_IP=127.0.0.1:4242

Diff for: main.go

+120-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package main
22

33
import (
4+
"bytes"
45
"database/sql"
6+
"encoding/json"
57
"fmt"
68
"io/ioutil"
79
"log"
810
"net"
11+
"net/http"
912
"os"
1013
"regexp"
1114
"strconv"
@@ -19,10 +22,14 @@ import (
1922
var conn net.Conn
2023
var db *sql.DB
2124

25+
// Only used if ENABLE_UNIQUE_METRIC_LIMIT is set to "true"
2226
var uniqueMetricLimit int
2327

2428
const defaultMetricLimit int = 100
2529

30+
// Only used if LOGSHUTTLE_URL is set
31+
var sentRejections map[string]map[string]int
32+
2633
func initDB() (err error) {
2734
var dberr error
2835
db, dberr = sql.Open("postgres", os.Getenv("DATABASE_URL"))
@@ -132,6 +139,98 @@ func sendMetric(v []string, t map[string]string, message string, tags [][]string
132139
fmt.Fprintf(conn, put)
133140
}
134141

142+
// checkPreviousRejections - Go through the rejection cache and report if we have reached the rejection limit for the app and metric
143+
func checkPreviousRejections(app string, metric string, metricType string) bool {
144+
limit, err := strconv.Atoi(os.Getenv("REJECT_MESSAGE_LIMIT"))
145+
if err != nil {
146+
limit = 1
147+
}
148+
149+
if sentRejections == nil {
150+
sentRejections = make(map[string]map[string]int)
151+
}
152+
153+
if sentRejections[app] == nil {
154+
sentRejections[app] = make(map[string]int)
155+
}
156+
157+
ok := (sentRejections[app][metricType+metric] < limit)
158+
sentRejections[app][metricType+metric]++
159+
160+
return ok
161+
}
162+
163+
func rejectMetric(app string, metric string, metricType string) {
164+
appParts := strings.SplitN(app, "-", 2)
165+
appName := appParts[0]
166+
appSpace := appParts[1]
167+
168+
// Replace all # characters with _ so that we don't send anything
169+
// to the logshuttle that might be considered a new metric
170+
metric = strings.Replace(metric, "#", "_", -1)
171+
172+
// Limit the number of times we report to the app logs
173+
if os.Getenv("REJECT_MESSAGE_LIMIT") != "" {
174+
if !checkPreviousRejections(app, metric, metricType) {
175+
if os.Getenv("DEBUG") == "true" {
176+
fmt.Println("Reject message limit reached for " + app + ": [" + metricType + "] " + metric)
177+
}
178+
return
179+
}
180+
}
181+
182+
logMessage := "Unique metrics limit exceeded. Metric discarded: [" + metricType + "] " + metric
183+
if os.Getenv("UNIQUE_METRIC_LIMIT_HELP") != "" {
184+
logMessage = "(" + os.Getenv("UNIQUE_METRIC_LIMIT_HELP") + ") " + logMessage
185+
}
186+
187+
rejectMessage := struct {
188+
Log string `json:"log"`
189+
Stream string `json:"stream"`
190+
Time time.Time `json:"time"`
191+
Kubernetes interface{} `json:"kubernetes"`
192+
Topic string `json:"topic"`
193+
}{
194+
Log: logMessage,
195+
Stream: "stdout",
196+
Time: time.Now(),
197+
Kubernetes: struct {
198+
PodName string `json:"pod_name"`
199+
ContainerName string `json:"container_name"`
200+
}{
201+
PodName: "akkeris/metrics",
202+
ContainerName: appName,
203+
},
204+
Topic: appSpace,
205+
}
206+
207+
var logshuttleURL string
208+
if os.Getenv("LOGSHUTTLE_URL") != "" {
209+
logshuttleURL = os.Getenv("LOGSHUTTLE_URL")
210+
} else {
211+
logshuttleURL = "http://logshuttle.akkeris-system.svc.cluster.local"
212+
}
213+
214+
jsonb, err := json.Marshal(rejectMessage)
215+
if err != nil {
216+
fmt.Println("Unable to send reject message to logshuttle")
217+
fmt.Println(err)
218+
return
219+
}
220+
221+
resp, err := http.Post(logshuttleURL+"/log-events", "application/json", bytes.NewBuffer(jsonb))
222+
if err != nil {
223+
fmt.Println("Unable to send reject message to logshuttle")
224+
fmt.Println(err)
225+
return
226+
}
227+
defer resp.Body.Close()
228+
229+
if resp.StatusCode < 200 || resp.StatusCode > 299 {
230+
fmt.Println("Error: " + strconv.Itoa(resp.StatusCode) + " response returned from logshuttle")
231+
}
232+
}
233+
135234
func main() {
136235
var err error
137236
conn, err = net.Dial("tcp", os.Getenv("OPENTSDB_IP"))
@@ -150,10 +249,12 @@ func main() {
150249

151250
server.Boot()
152251

153-
err = initDB()
154-
if err != nil {
155-
fmt.Println("Error establishing database connection: " + err.Error())
156-
return
252+
if os.Getenv("ENABLE_UNIQUE_METRIC_LIMIT") == "true" {
253+
err = initDB()
254+
if err != nil {
255+
fmt.Println("Error establishing database connection: " + err.Error())
256+
return
257+
}
157258
}
158259

159260
if os.Getenv("UNIQUE_METRIC_LIMIT") != "" {
@@ -178,14 +279,28 @@ func main() {
178279
t := make(map[string]string)
179280
t["app"] = logParts["hostname"].(string)
180281

282+
// Useful for testing:
283+
// t["app"] = logParts["app_name"].(string)
284+
181285
measurements := re.FindAllStringSubmatch(message, -1)
182286
tags := tagsRe.FindAllStringSubmatch(message, -1)
183287

184288
for _, v := range measurements {
185289
t["metric"] = v[2]
186-
ok := checkMetric(t["app"], t["metric"])
290+
291+
// Only check metric limit if ENABLE_UNIQUE_METRIC_LIMIT is set to "true"
292+
ok := true
293+
if os.Getenv("ENABLE_UNIQUE_METRIC_LIMIT") == "true" {
294+
ok = checkMetric(t["app"], t["metric"])
295+
}
296+
187297
if ok {
188298
sendMetric(v, t, message, tags)
299+
} else {
300+
// Only send rejection message if LOGSHUTTLE_URL is set
301+
if os.Getenv("LOGSHUTTLE_URL") != "" {
302+
rejectMetric(t["app"], t["metric"], v[1])
303+
}
189304
}
190305
}
191306
}

0 commit comments

Comments
 (0)