The following Go file can be compiled into a continuous external collector that will query a MSSQL server database that uses the StackExchange.Exceptional schema. It will query multiple servers/databases for all exceptions since UTC 00:00 to convert the raw entries into a counter. It also uses the bosun.org/metadata package to include metadata for the exceptional.exceptions.count metric.
/*
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
}
}