用 Go 和 Metadata 編寫的 StackExchange.Exceptional 收集器
可以將以下 Go 檔案編譯為連續的外部收集器,該收集器將查詢使用 StackExchange.Exceptional 模式的 MSSQL 伺服器資料庫。它將查詢多個伺服器/資料庫以查詢自 UTC 00:00 以來的所有異常,以將原始條目轉換為計數器。它還使用 bosun.org/metadata 包來包含 exceptions.exceptions.count 指標的後設資料。
/*
Exceptional is an scollector external collector for StackExchange.Exceptional.
*/
package main
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"strings"
"time"
"bosun.org/metadata"
"bosun.org/opentsdb"
_ "github.com/denisenkom/go-mssqldb"
)
func mssqlConnect(server, database, user, pass, port string) (*sql.DB, error) {
dsn := fmt.Sprintf("server=%s;port=%s;database=%s;user id=%s;password=%s", server, port, database, user, pass)
return sql.Open("mssql", dsn)
}
type Exceptions struct {
GUID string
ApplicationName string
MachineName string
CreationDate time.Time
Type string
IsProtected int
Host string
Url string
HTTPMethod string
IPAddress string
Source string
Message string
Detail string
StatusCode int
SQL string
DeletionDate time.Time
FullJson string
ErrorHash int
DuplicateCount int
}
type ExceptionsCount struct {
ApplicationName string
MachineName string
Count int64
Source string
}
type ExceptionsDB struct {
Server string
DBName string
DBPassword string
DBPort string
Source string
}
const (
defaultPassword = "EnterPasswordHere"
defaultPort = "1433"
metric = "exceptional.exceptions.count"
descMetric = "The number of exceptions thrown per second by applications and machines. Data is queried from multiple sources. See status instances for details on exceptions."
)
func main() {
mds := []metadata.Metasend{
{
Metric: metric,
Name: "rate",
Value: "counter",
},
{
Metric: metric,
Name: "unit",
Value: metadata.Error,
},
{
Metric: metric,
Name: "desc",
Value: descMetric,
},
}
for _, m := range mds {
b, err := json.Marshal(m)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(b))
}
instances := [...]ExceptionsDB{
{"NY_AG", "NY.Exceptions", defaultPassword, defaultPort, "NY_Status"},
{"CO-SQL", "CO.Exceptions", defaultPassword, defaultPort, "CO_Status"},
{"NY-INTSQL", "Int.Exceptions", defaultPassword, defaultPort, "INT_Status"},
}
for _, exdb := range instances {
go run(exdb)
}
select {}
}
func run(exdb ExceptionsDB) {
const interval = time.Second * 30
query := func() {
// Database name is the same as the username
db, err := mssqlConnect(exdb.Server, exdb.DBName, exdb.DBName, exdb.DBPassword, exdb.DBPort)
if err != nil {
log.Println(err)
}
defer db.Close()
var results []ExceptionsCount
sqlQuery := `
SELECT ApplicationName, MachineName, MAX(Count) as Count FROM
(
--New since UTC rollover
SELECT ApplicationName, MachineName, Sum(DuplicateCount) as Count from Exceptions
WHERE CreationDate > CONVERT (date, GETUTCDATE())
GROUP BY MachineName, ApplicationName
UNION --Zero out any app/machine combos that had exceptions in last 24 hours
SELECT DISTINCT ex.ApplicationName, ex.MachineName, 0 as Count from Exceptions ex WHERE ex.CreationDate Between Convert(Date, GETUTCDATE()-1) And Convert(Date, GETUTCDATE())
) as T
GROUP By T.MachineName, T.ApplicationName`
rows, err := db.Query(sqlQuery)
if err != nil {
log.Println(err)
return
}
defer rows.Close()
for rows.Next() {
var r ExceptionsCount
if err := rows.Scan(&r.ApplicationName, &r.MachineName, &r.Count); err != nil {
log.Println(err)
continue
}
r.Source = exdb.Source
results = append(results, r)
}
if err := rows.Err(); err != nil {
log.Println(err)
}
if len(results) > 0 {
now := time.Now().Unix()
for _, r := range results {
application, err := opentsdb.Clean(r.ApplicationName)
if err != nil {
log.Println(err)
continue
}
db := opentsdb.DataPoint{
Metric: metric,
Timestamp: now,
Value: r.Count,
Tags: opentsdb.TagSet{
"application": application,
"machine": strings.ToLower(r.MachineName),
"source": r.Source,
},
}
b, err := db.MarshalJSON()
if err != nil {
log.Println(err)
continue
}
fmt.Println(string(b))
}
}
}
for {
wait := time.After(interval)
query()
<-wait
}
}