better logging (logrus), verbose (-v) mode, index page show create service btn, install.sh script, Dockerfile go version to 1.13, UI fixes

pull/335/head
hunterlong 2019-12-28 01:01:07 -08:00
parent b534652064
commit 15063734a5
62 changed files with 1016 additions and 616 deletions

View File

@ -1,12 +1,11 @@
FROM golang:1.12-alpine as base
FROM golang:1.13-alpine as base
LABEL maintainer="Hunter Long (https://github.com/hunterlong)"
ARG VERSION
ENV DEP_VERSION v0.5.0
RUN apk add --no-cache libstdc++ gcc g++ make git ca-certificates linux-headers wget curl jq libsass
RUN curl -L -s https://assets.statping.com/sass -o /usr/local/bin/sass && \
chmod +x /usr/local/bin/sass
WORKDIR /go/src/github.com/hunterlong/statping
ADD Makefile Gopkg.* /go/src/github.com/hunterlong/statping/
ADD Makefile go.mod /go/src/github.com/hunterlong/statping/
RUN go mod vendor && \
make dev-deps
ADD . /go/src/github.com/hunterlong/statping

View File

@ -1,14 +1,14 @@
VERSION=$(shell cat version.txt)
SIGN_KEY=B76D61FAA6DB759466E83D9964B9C6AAE2D55278
BINARY_NAME=statping
GOPATH:=$(GOPATH)
GOCMD=go
GOBUILD=$(GOCMD) build -a
GOTEST=$(GOCMD) test
GOGET=$(GOCMD) get
GOVERSION=1.13.1
GOINSTALL=$(GOCMD) install
XGO=GOPATH=$(GOPATH) xgo -go $(GOVERSION) --dest=build
GOPATH:=$(GOPATH)
XGO=$(GOPATH) xgo -go $(GOVERSION) --dest=build
BUILDVERSION=-ldflags "-X main.VERSION=${VERSION} -X main.COMMIT=$(TRAVIS_COMMIT)"
RICE=$(GOPATH)/bin/rice
PATH:=/usr/local/bin:$(GOPATH)/bin:$(PATH)

View File

@ -43,9 +43,9 @@ func catchCLI(args []string) error {
switch args[0] {
case "version":
if COMMIT != "" {
fmt.Printf("Statping v%v (%v)\n", VERSION, COMMIT)
fmt.Printf("%v (%v)\n", VERSION, COMMIT)
} else {
fmt.Printf("Statping v%v\n", VERSION)
fmt.Printf("%v\n", VERSION)
}
return errors.New("end")
case "assets":
@ -60,18 +60,7 @@ func catchCLI(args []string) error {
}
return errors.New("end")
case "update":
var err error
var gitCurrent githubResponse
if gitCurrent, err = checkGithubUpdates(); err != nil {
return err
}
fmt.Printf("Statping Version: v%v\nLatest Version: %v\n", VERSION, gitCurrent.TagName)
if VERSION != gitCurrent.TagName[1:] {
fmt.Printf("You don't have the latest version v%v!\nDownload the latest release at: https://github.com/hunterlong/statping\n", gitCurrent.TagName[1:])
} else {
fmt.Printf("You have the latest version of Statping!\n")
}
return errors.New("end")
return updateDisplay()
case "test":
cmd := args[1]
switch cmd {
@ -84,16 +73,16 @@ func catchCLI(args []string) error {
fmt.Printf("Statping v%v Exporting Static 'index.html' page...\n", VERSION)
utils.InitLogs()
if core.Configs, err = core.LoadConfigFile(dir); err != nil {
utils.Log(4, "config.yml file not found")
utils.Log.Errorln("config.yml file not found")
return err
}
indexSource := ExportIndexHTML()
//core.CloseDB()
if err = utils.SaveFile(dir+"/index.html", indexSource); err != nil {
utils.Log(4, err)
utils.Log.Errorln(err)
return err
}
utils.Log(1, "Exported Statping index page: 'index.html'")
utils.Log.Infoln("Exported Statping index page: 'index.html'")
case "help":
HelpEcho()
return errors.New("end")
@ -133,8 +122,8 @@ func catchCLI(args []string) error {
}
return errors.New("end")
case "run":
utils.Log(1, "Running 1 time and saving to database...")
RunOnce()
utils.Log.Infoln("Running 1 time and saving to database...")
runOnce()
//core.CloseDB()
fmt.Println("Check is complete.")
return errors.New("end")
@ -142,7 +131,7 @@ func catchCLI(args []string) error {
fmt.Println("Statping Environment Variable")
envs, err := godotenv.Read(".env")
if err != nil {
utils.Log(4, "No .env file found in current directory.")
utils.Log.Errorln("No .env file found in current directory.")
return err
}
for k, e := range envs {
@ -170,16 +159,33 @@ func ExportIndexHTML() []byte {
return w.Body.Bytes()
}
// RunOnce will initialize the Statping application and check each service 1 time, will not run HTTP server
func RunOnce() {
func updateDisplay() error {
var err error
var gitCurrent githubResponse
if gitCurrent, err = checkGithubUpdates(); err != nil {
return err
}
fmt.Printf("Statping Version: v%v\nLatest Version: %v\n", VERSION, gitCurrent.TagName)
if VERSION != gitCurrent.TagName[1:] {
fmt.Printf("\n New Update %v Available\n", gitCurrent.TagName[1:])
fmt.Printf("Update Command:\n")
fmt.Printf("curl -o- -L https://statping.com/install.sh | bash\n\n")
} else {
fmt.Printf("You have the latest version of Statping!\n")
}
return errors.New("end")
}
// runOnce will initialize the Statping application and check each service 1 time, will not run HTTP server
func runOnce() {
var err error
core.Configs, err = core.LoadConfigFile(utils.Directory)
if err != nil {
utils.Log(4, "config.yml file not found")
utils.Log.Errorln("config.yml file not found")
}
err = core.Configs.Connect(false, utils.Directory)
if err != nil {
utils.Log(4, err)
utils.Log.Errorln(err)
}
core.CoreApp, err = core.SelectCore()
if err != nil {
@ -187,7 +193,7 @@ func RunOnce() {
}
_, err = core.CoreApp.SelectAllServices(true)
if err != nil {
utils.Log(4, err)
utils.Log.Errorln(err)
}
for _, out := range core.CoreApp.Services {
out.Check(true)
@ -214,6 +220,8 @@ func HelpEcho() {
fmt.Printf("Flags:\n")
fmt.Println(" -ip 127.0.0.1 - Run HTTP server on specific IP address (default: localhost)")
fmt.Println(" -port 8080 - Run HTTP server on Port (default: 8080)")
fmt.Println(" -verbose 1 - Verbose mode levels 1 - 4 (default: 1)")
fmt.Println(" -env path/debug.env - Optional .env file to set as environment variables while running server")
fmt.Printf("Environment Variables:\n")
fmt.Println(" PORT - Set the outgoing port for the HTTP server (or use -port)")
fmt.Println(" IP - Bind a specific IP address to the HTTP server (or use -ip)")
@ -241,7 +249,7 @@ func HelpEcho() {
func checkGithubUpdates() (githubResponse, error) {
var gitResp githubResponse
url := "https://api.github.com/repos/hunterlong/statping/releases/latest"
contents, _, err := utils.HttpRequest(url, "GET", nil, nil, nil, time.Duration(10*time.Second), true)
contents, _, err := utils.HttpRequest(url, "GET", nil, nil, nil, time.Duration(2*time.Second), true)
if err != nil {
return githubResponse{}, err
}

View File

@ -52,7 +52,7 @@ func TestStartServerCommand(t *testing.T) {
func TestVersionCommand(t *testing.T) {
c := testcli.Command("statping", "version")
c.Run()
assert.True(t, c.StdoutContains("Statping v"+VERSION))
assert.True(t, c.StdoutContains(VERSION))
}
func TestHelpCommand(t *testing.T) {
@ -90,7 +90,7 @@ func TestUpdateCommand(t *testing.T) {
commandAndSleep(cmd, time.Duration(15*time.Second), got)
gg, _ := <-got
t.Log(gg)
assert.Contains(t, gg, "Statping")
assert.Contains(t, gg, VERSION)
}
func TestAssetsCommand(t *testing.T) {

View File

@ -16,13 +16,14 @@
package main
import (
"github.com/hunterlong/statping/utils"
"flag"
"fmt"
"github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/handlers"
"github.com/hunterlong/statping/plugin"
"github.com/hunterlong/statping/source"
"github.com/hunterlong/statping/utils"
"github.com/joho/godotenv"
"os"
"os/signal"
@ -35,7 +36,8 @@ var (
// COMMIT stores the git commit hash for this version of Statping
COMMIT string
ipAddress string
UsingDotEnv bool
envFile string
verboseMode int
port int
)
@ -47,17 +49,21 @@ func init() {
// -ip = 0.0.0.0 IP address for outgoing HTTP server
// -port = 8080 Port number for outgoing HTTP server
func parseFlags() {
ip := flag.String("ip", "0.0.0.0", "IP address to run the Statping HTTP server")
p := flag.Int("port", 8080, "Port to run the HTTP server")
flag.StringVar(&ipAddress, "ip", "0.0.0.0", "IP address to run the Statping HTTP server")
flag.StringVar(&envFile, "env", "", "IP address to run the Statping HTTP server")
flag.IntVar(&port, "port", 8080, "Port to run the HTTP server")
flag.IntVar(&verboseMode, "verbose", 1, "Run in verbose mode to see detailed logs (1 - 4)")
flag.Parse()
ipAddress = *ip
port = *p
if os.Getenv("PORT") != "" {
port = int(utils.ToInt(os.Getenv("PORT")))
}
if os.Getenv("IP") != "" {
ipAddress = os.Getenv("IP")
}
if os.Getenv("VERBOSE") != "" {
verboseMode = int(utils.ToInt(os.Getenv("VERBOSE")))
}
}
// main will run the Statping application
@ -67,6 +73,7 @@ func main() {
parseFlags()
loadDotEnvs()
source.Assets()
utils.VerboseMode = verboseMode
if err := utils.InitLogs(); err != nil {
fmt.Printf("Statping Log Error: \n %v\n", err)
os.Exit(2)
@ -83,33 +90,39 @@ func main() {
os.Exit(1)
}
}
utils.Log(1, fmt.Sprintf("Starting Statping v%v", VERSION))
utils.Log.Info(fmt.Sprintf("Starting Statping v%v", VERSION))
updateDisplay()
core.Configs, err = core.LoadConfigFile(utils.Directory)
if err != nil {
utils.Log(3, err)
utils.Log.Errorln(err)
core.SetupMode = true
utils.Log(1, handlers.RunHTTPServer(ipAddress, port))
utils.Log.Infoln(handlers.RunHTTPServer(ipAddress, port))
os.Exit(1)
}
mainProcess()
}
// Close will gracefully stop the database connection, and log file
func Close() {
core.CloseDB()
utils.CloseLogs()
}
// sigterm will attempt to close the database connections gracefully
func sigterm() {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
<-sigs
core.CloseDB()
Close()
os.Exit(1)
}
// loadDotEnvs attempts to load database configs from a '.env' file in root directory
func loadDotEnvs() error {
err := godotenv.Load()
err := godotenv.Load(envFile)
if err == nil {
utils.Log(1, "Environment file '.env' Loaded")
UsingDotEnv = true
utils.Log.Infoln("Environment file '.env' Loaded")
}
return err
}
@ -120,7 +133,7 @@ func mainProcess() {
var err error
err = core.Configs.Connect(false, dir)
if err != nil {
utils.Log(4, fmt.Sprintf("could not connect to database: %v", err))
utils.Log.Errorln(fmt.Sprintf("could not connect to database: %v", err))
}
core.Configs.MigrateDatabase()
core.InitApp()

View File

@ -32,7 +32,7 @@ import (
// checkServices will start the checking go routine for each service
func checkServices() {
utils.Log(1, fmt.Sprintf("Starting monitoring process for %v Services", len(CoreApp.Services)))
log.Infoln(fmt.Sprintf("Starting monitoring process for %v Services", len(CoreApp.Services)))
for _, ser := range CoreApp.Services {
//go obj.StartCheckins()
go ser.CheckQueue(true)
@ -60,7 +60,7 @@ CheckLoop:
for {
select {
case <-s.Running:
utils.Log(1, fmt.Sprintf("Stopping service: %v", s.Name))
log.Infoln(fmt.Sprintf("Stopping service: %v", s.Name))
break CheckLoop
case <-time.After(s.SleepDuration):
s.Check(record)
@ -229,7 +229,7 @@ func (s *Service) checkHttp(record bool) *Service {
if s.Expected.String != "" {
match, err := regexp.MatchString(s.Expected.String, string(content))
if err != nil {
utils.Log(2, fmt.Sprintf("Service %v expected: %v to match %v", s.Name, string(content), s.Expected.String))
log.Warnln(fmt.Sprintf("Service %v expected: %v to match %v", s.Name, string(content), s.Expected.String))
}
if !match {
if record {
@ -259,8 +259,8 @@ func recordSuccess(s *Service) {
PingTime: s.PingTime,
CreatedAt: time.Now(),
}
utils.Log(1, fmt.Sprintf("Service %v Successful Response: %0.2f ms | Lookup in: %0.2f ms", s.Name, hit.Latency*1000, hit.PingTime*1000))
s.CreateHit(hit)
log.WithFields(utils.ToFields(hit, s.Select())).Infoln(fmt.Sprintf("Service %v Successful Response: %0.2f ms | Lookup in: %0.2f ms", s.Name, hit.Latency*1000, hit.PingTime*1000))
notifier.OnSuccess(s.Service)
s.Online = true
s.SuccessNotified = true
@ -268,18 +268,18 @@ func recordSuccess(s *Service) {
// recordFailure will create a new 'Failure' record in the database for a offline service
func recordFailure(s *Service, issue string) {
fail := &Failure{&types.Failure{
fail := &types.Failure{
Service: s.Id,
Issue: issue,
PingTime: s.PingTime,
CreatedAt: time.Now(),
ErrorCode: s.LastStatusCode,
}}
utils.Log(2, fmt.Sprintf("Service %v Failing: %v | Lookup in: %0.2f ms", s.Name, issue, fail.PingTime*1000))
}
log.WithFields(utils.ToFields(fail, s.Select())).Warnln(fmt.Sprintf("Service %v Failing: %v | Lookup in: %0.2f ms", s.Name, issue, fail.PingTime*1000))
s.CreateFailure(fail)
s.Online = false
s.SuccessNotified = false
s.UpdateNotify = CoreApp.UpdateNotify.Bool
s.DownText = s.DowntimeText()
notifier.OnFailure(s.Service, fail.Failure)
notifier.OnFailure(s.Service, fail)
}

View File

@ -47,14 +47,14 @@ CheckinLoop:
for {
select {
case <-c.Running:
utils.Log(1, fmt.Sprintf("Stopping checkin routine: %v", c.Name))
log.Infoln(fmt.Sprintf("Stopping checkin routine: %v", c.Name))
c.Failing = false
break CheckinLoop
case <-time.After(reCheck):
utils.Log(1, fmt.Sprintf("Checkin %v is expected at %v, checking every %v", c.Name, utils.FormatDuration(c.Expected()), utils.FormatDuration(c.Period())))
log.Infoln(fmt.Sprintf("Checkin %v is expected at %v, checking every %v", c.Name, utils.FormatDuration(c.Expected()), utils.FormatDuration(c.Period())))
if c.Expected().Seconds() <= 0 {
issue := fmt.Sprintf("Checkin %v is failing, no request since %v", c.Name, c.Last().CreatedAt)
utils.Log(3, issue)
log.Errorln(issue)
c.CreateFailure()
}
reCheck = c.Period()
@ -143,7 +143,7 @@ func (c *Checkin) Grace() time.Duration {
// Expected returns the duration of when the serviec should receive a Checkin
func (c *Checkin) Expected() time.Duration {
last := c.Last().CreatedAt
now := time.Now()
now := utils.Now()
lastDir := now.Sub(last)
sub := time.Duration(c.Period() - lastDir)
return sub
@ -213,7 +213,7 @@ func (c *Checkin) Create() (int64, error) {
c.ApiKey = utils.RandomString(7)
row := checkinDB().Create(&c)
if row.Error != nil {
utils.Log(2, row.Error)
log.Warnln(row.Error)
return 0, row.Error
}
service := SelectService(c.ServiceId)
@ -227,7 +227,7 @@ func (c *Checkin) Create() (int64, error) {
func (c *Checkin) Update() (int64, error) {
row := checkinDB().Update(&c)
if row.Error != nil {
utils.Log(2, row.Error)
log.Warnln(row.Error)
return 0, row.Error
}
return c.Id, row.Error
@ -236,11 +236,11 @@ func (c *Checkin) Update() (int64, error) {
// Create will create a new successful checkinHit
func (c *CheckinHit) Create() (int64, error) {
if c.CreatedAt.IsZero() {
c.CreatedAt = time.Now()
c.CreatedAt = utils.Now()
}
row := checkinHitsDB().Create(&c)
if row.Error != nil {
utils.Log(2, row.Error)
log.Warnln(row.Error)
return 0, row.Error
}
return c.Id, row.Error
@ -248,13 +248,13 @@ func (c *CheckinHit) Create() (int64, error) {
// Ago returns the duration of time between now and the last successful checkinHit
func (c *CheckinHit) Ago() string {
got, _ := timeago.TimeAgoWithTime(time.Now(), c.CreatedAt)
got, _ := timeago.TimeAgoWithTime(utils.Now(), c.CreatedAt)
return got
}
// RecheckCheckinFailure will check if a Service Checkin has been reported yet
func (c *Checkin) RecheckCheckinFailure(guard chan struct{}) {
between := time.Now().Sub(time.Now()).Seconds()
between := utils.Now().Sub(utils.Now()).Seconds()
if between > float64(c.Interval) {
fmt.Println("rechecking every 15 seconds!")
time.Sleep(15 * time.Second)

View File

@ -34,7 +34,7 @@ type ErrorResponse struct {
func LoadConfigFile(directory string) (*DbConfig, error) {
var configs *DbConfig
if os.Getenv("DB_CONN") != "" {
utils.Log(1, "DB_CONN environment variable was found, waiting for database...")
log.Infoln("DB_CONN environment variable was found, waiting for database...")
return LoadUsingEnv()
}
file, err := ioutil.ReadFile(directory + "/config.yml")
@ -66,18 +66,18 @@ func LoadUsingEnv() (*DbConfig, error) {
err = Configs.Connect(true, utils.Directory)
if err != nil {
utils.Log(4, err)
log.Errorln(err)
return nil, err
}
Configs.Save()
exists := DbSession.HasTable("core")
if !exists {
utils.Log(1, fmt.Sprintf("Core database does not exist, creating now!"))
log.Infoln(fmt.Sprintf("Core database does not exist, creating now!"))
Configs.DropDatabase()
Configs.CreateDatabase()
CoreApp, err = Configs.InsertCore()
if err != nil {
utils.Log(3, err)
log.Errorln(err)
}
username := os.Getenv("ADMIN_USER")
@ -195,7 +195,7 @@ func SampleData() error {
func DeleteConfig() error {
err := os.Remove(utils.Directory + "/config.yml")
if err != nil {
utils.Log(3, err)
log.Errorln(err)
return err
}
return nil

View File

@ -40,6 +40,7 @@ var (
CoreApp *Core // CoreApp is a global variable that contains many elements
SetupMode bool // SetupMode will be true if Statping does not have a database connection
VERSION string // VERSION is set on build automatically by setting a -ldflag
log = utils.Log.WithField("type", "core")
)
func init() {

View File

@ -134,6 +134,7 @@ func TestEnvToConfig(t *testing.T) {
os.Setenv("DESCRIPTION", "Testing Statping")
os.Setenv("ADMIN_USER", "admin")
os.Setenv("ADMIN_PASS", "admin123")
os.Setenv("VERBOSE", "true")
config, err := EnvToConfig()
assert.Nil(t, err)
assert.Equal(t, config.DbConn, "sqlite")

View File

@ -217,7 +217,7 @@ func (db *DbConfig) Connect(retry bool, location string) error {
dbSession, err := gorm.Open(dbType, conn)
if err != nil {
if retry {
utils.Log(1, fmt.Sprintf("Database connection to '%v' is not available, trying again in 5 seconds...", Configs.DbHost))
log.Infoln(fmt.Sprintf("Database connection to '%v' is not available, trying again in 5 seconds...", Configs.DbHost))
return db.waitForDb()
} else {
return err
@ -229,7 +229,10 @@ func (db *DbConfig) Connect(retry bool, location string) error {
err = dbSession.DB().Ping()
if err == nil {
DbSession = dbSession
utils.Log(1, fmt.Sprintf("Database %v connection was successful.", dbType))
if utils.VerboseMode >= 4 {
DbSession.LogMode(true).Debug().SetLogger(log)
}
log.Infoln(fmt.Sprintf("Database %v connection was successful.", dbType))
}
return err
}
@ -244,7 +247,7 @@ func (db *DbConfig) waitForDb() error {
// this function is currently set to delete records 7+ days old every 60 minutes
func DatabaseMaintence() {
for range time.Tick(60 * time.Minute) {
utils.Log(1, "Checking for database records older than 3 months...")
log.Infoln("Checking for database records older than 3 months...")
since := time.Now().AddDate(0, -3, 0).UTC()
DeleteAllSince("failures", since)
DeleteAllSince("hits", since)
@ -256,7 +259,7 @@ func DeleteAllSince(table string, date time.Time) {
sql := fmt.Sprintf("DELETE FROM %v WHERE created_at < '%v';", table, date.Format("2006-01-02"))
db := DbSession.Exec(sql)
if db.Error != nil {
utils.Log(2, db.Error)
log.Warnln(db.Error)
}
}
@ -265,12 +268,12 @@ func (db *DbConfig) Update() error {
var err error
config, err := os.Create(utils.Directory + "/config.yml")
if err != nil {
utils.Log(4, err)
log.Errorln(err)
return err
}
data, err := yaml.Marshal(db)
if err != nil {
utils.Log(3, err)
log.Errorln(err)
return err
}
config.WriteString(string(data))
@ -283,14 +286,14 @@ func (db *DbConfig) Save() (*DbConfig, error) {
var err error
config, err := os.Create(utils.Directory + "/config.yml")
if err != nil {
utils.Log(4, err)
log.Errorln(err)
return nil, err
}
db.ApiKey = utils.NewSHA1Hash(16)
db.ApiSecret = utils.NewSHA1Hash(16)
data, err := yaml.Marshal(db)
if err != nil {
utils.Log(3, err)
log.Errorln(err)
return nil, err
}
config.WriteString(string(data))
@ -315,14 +318,14 @@ func (c *DbConfig) CreateCore() *Core {
}
CoreApp, err := SelectCore()
if err != nil {
utils.Log(4, err)
log.Errorln(err)
}
return CoreApp
}
// DropDatabase will DROP each table Statping created
func (db *DbConfig) DropDatabase() error {
utils.Log(1, "Dropping Database Tables...")
log.Infoln("Dropping Database Tables...")
err := DbSession.DropTableIfExists("checkins")
err = DbSession.DropTableIfExists("checkin_hits")
err = DbSession.DropTableIfExists("notifications")
@ -340,7 +343,7 @@ func (db *DbConfig) DropDatabase() error {
// CreateDatabase will CREATE TABLES for each of the Statping elements
func (db *DbConfig) CreateDatabase() error {
var err error
utils.Log(1, "Creating Database Tables...")
log.Infoln("Creating Database Tables...")
for _, table := range DbModels {
if err := DbSession.CreateTable(table); err.Error != nil {
return err.Error
@ -349,7 +352,7 @@ func (db *DbConfig) CreateDatabase() error {
if err := DbSession.Table("core").CreateTable(&types.Core{}); err.Error != nil {
return err.Error
}
utils.Log(1, "Statping Database Created")
log.Infoln("Statping Database Created")
return err
}
@ -357,7 +360,7 @@ func (db *DbConfig) CreateDatabase() error {
// This function will NOT remove previous records, tables or columns from the database.
// If this function has an issue, it will ROLLBACK to the previous state.
func (db *DbConfig) MigrateDatabase() error {
utils.Log(1, "Migrating Database Tables...")
log.Infoln("Migrating Database Tables...")
tx := DbSession.Begin()
defer func() {
if r := recover(); r != nil {
@ -372,9 +375,9 @@ func (db *DbConfig) MigrateDatabase() error {
}
if err := tx.Table("core").AutoMigrate(&types.Core{}); err.Error != nil {
tx.Rollback()
utils.Log(3, fmt.Sprintf("Statping Database could not be migrated: %v", tx.Error))
log.Errorln(fmt.Sprintf("Statping Database could not be migrated: %v", tx.Error))
return tx.Error
}
utils.Log(1, "Statping Database Migrated")
log.Infoln("Statping Database Migrated")
return tx.Commit().Error
}

View File

@ -20,7 +20,6 @@ import (
"encoding/json"
"github.com/hunterlong/statping/source"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
"html/template"
)
@ -28,7 +27,7 @@ import (
func ExportChartsJs() string {
render, err := source.JsBox.String("charts.js")
if err != nil {
utils.Log(4, err)
log.Errorln(err)
}
t := template.New("charts")
t.Funcs(template.FuncMap{
@ -39,7 +38,7 @@ func ExportChartsJs() string {
t.Parse(render)
var tpl bytes.Buffer
if err := t.Execute(&tpl, CoreApp.Services); err != nil {
utils.Log(3, err)
log.Errorln(err)
}
result := tpl.String()
return result

View File

@ -19,7 +19,6 @@ import (
"fmt"
"github.com/ararog/timeago"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
"sort"
"strings"
"time"
@ -35,16 +34,15 @@ const (
)
// CreateFailure will create a new Failure record for a service
func (s *Service) CreateFailure(fail types.FailureInterface) (int64, error) {
f := fail.(*Failure)
func (s *Service) CreateFailure(f *types.Failure) (int64, error) {
f.Service = s.Id
row := failuresDB().Create(f)
if row.Error != nil {
utils.Log(3, row.Error)
log.Errorln(row.Error)
return 0, row.Error
}
sort.Sort(types.FailSort(s.Failures))
s.Failures = append(s.Failures, f)
//s.Failures = append(s.Failures, f)
if len(s.Failures) > limitedFailures {
s.Failures = s.Failures[1:]
}
@ -57,7 +55,7 @@ func (s *Service) AllFailures() []*Failure {
col := failuresDB().Where("service = ?", s.Id).Not("method = 'checkin'").Order("id desc")
err := col.Find(&fails)
if err.Error != nil {
utils.Log(3, fmt.Sprintf("Issue getting failures for service %v, %v", s.Name, err))
log.Errorln(fmt.Sprintf("Issue getting failures for service %v, %v", s.Name, err))
return nil
}
return fails
@ -67,7 +65,7 @@ func (s *Service) AllFailures() []*Failure {
func (s *Service) DeleteFailures() {
err := DbSession.Exec(`DELETE FROM failures WHERE service = ?`, s.Id)
if err.Error != nil {
utils.Log(3, fmt.Sprintf("failed to delete all failures: %v", err))
log.Errorln(fmt.Sprintf("failed to delete all failures: %v", err))
}
s.Failures = nil
}
@ -119,7 +117,7 @@ func CountFailures() uint64 {
var count uint64
err := failuresDB().Count(&count)
if err.Error != nil {
utils.Log(2, err.Error)
log.Warnln(err.Error)
return 0
}
return count

View File

@ -17,7 +17,6 @@ package core
import (
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
"time"
)
@ -29,7 +28,7 @@ type Hit struct {
func (s *Service) CreateHit(h *types.Hit) (int64, error) {
db := hitsDB().Create(&h)
if db.Error != nil {
utils.Log(2, db.Error)
log.Warnln(db.Error)
return 0, db.Error
}
return h.Id, db.Error

View File

@ -18,7 +18,6 @@ package core
import (
"fmt"
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
"time"
)
@ -64,7 +63,7 @@ func (m *Message) Create() (int64, error) {
m.CreatedAt = time.Now().UTC()
db := messagesDb().Create(m)
if db.Error != nil {
utils.Log(3, fmt.Sprintf("Failed to create message %v #%v: %v", m.Title, m.Id, db.Error))
log.Errorln(fmt.Sprintf("Failed to create message %v #%v: %v", m.Title, m.Id, db.Error))
return 0, db.Error
}
return m.Id, nil
@ -80,7 +79,7 @@ func (m *Message) Delete() error {
func (m *Message) Update() (*Message, error) {
db := messagesDb().Update(m)
if db.Error != nil {
utils.Log(3, fmt.Sprintf("Failed to update message %v #%v: %v", m.Title, m.Id, db.Error))
log.Errorln(fmt.Sprintf("Failed to update message %v #%v: %v", m.Title, m.Id, db.Error))
return nil, db.Error
}
return m, nil

View File

@ -54,7 +54,9 @@ sendMessages:
for _, comm := range AllCommunications {
if isType(comm, new(BasicEvents)) && isEnabled(comm) && (s.Online || inLimits(comm)) {
notifier := comm.(Notifier).Select()
utils.Log(1, fmt.Sprintf("Sending [OnFailure] '%v' notification for service %v", notifier.Method, s.Name))
log.
WithField("trigger", "OnFailure").
WithFields(utils.ToFields(notifier, s)).Infoln(fmt.Sprintf("Sending [OnFailure] '%v' notification for service %v", notifier.Method, s.Name))
comm.(BasicEvents).OnFailure(s, f)
}
}
@ -74,7 +76,9 @@ func OnSuccess(s *types.Service) {
for _, comm := range AllCommunications {
if isType(comm, new(BasicEvents)) && isEnabled(comm) && (!s.Online || inLimits(comm)) {
notifier := comm.(Notifier).Select()
utils.Log(1, fmt.Sprintf("Sending [OnSuccess] '%v' notification for service %v", notifier.Method, s.Name))
log.
WithField("trigger", "OnSuccess").
WithFields(utils.ToFields(notifier, s)).Infoln(fmt.Sprintf("Sending [OnSuccess] '%v' notification for service %v", notifier.Method, s.Name))
comm.(BasicEvents).OnSuccess(s)
}
}
@ -84,7 +88,9 @@ func OnSuccess(s *types.Service) {
func OnNewService(s *types.Service) {
for _, comm := range AllCommunications {
if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) {
utils.Log(1, fmt.Sprintf("Sending new service notification for service %v", s.Name))
log.
WithField("trigger", "OnNewService").
Infoln(fmt.Sprintf("Sending new service notification for service %v", s.Name))
comm.(ServiceEvents).OnNewService(s)
}
}
@ -97,7 +103,7 @@ func OnUpdatedService(s *types.Service) {
}
for _, comm := range AllCommunications {
if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) {
utils.Log(1, fmt.Sprintf("Sending updated service notification for service %v", s.Name))
log.Infoln(fmt.Sprintf("Sending updated service notification for service %v", s.Name))
comm.(ServiceEvents).OnUpdatedService(s)
}
}
@ -110,7 +116,7 @@ func OnDeletedService(s *types.Service) {
}
for _, comm := range AllCommunications {
if isType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) {
utils.Log(1, fmt.Sprintf("Sending deleted service notification for service %v", s.Name))
log.Infoln(fmt.Sprintf("Sending deleted service notification for service %v", s.Name))
comm.(ServiceEvents).OnDeletedService(s)
}
}
@ -120,7 +126,7 @@ func OnDeletedService(s *types.Service) {
func OnNewUser(u *types.User) {
for _, comm := range AllCommunications {
if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) {
utils.Log(1, fmt.Sprintf("Sending new user notification for user %v", u.Username))
log.Infoln(fmt.Sprintf("Sending new user notification for user %v", u.Username))
comm.(UserEvents).OnNewUser(u)
}
}
@ -130,7 +136,7 @@ func OnNewUser(u *types.User) {
func OnUpdatedUser(u *types.User) {
for _, comm := range AllCommunications {
if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) {
utils.Log(1, fmt.Sprintf("Sending updated user notification for user %v", u.Username))
log.Infoln(fmt.Sprintf("Sending updated user notification for user %v", u.Username))
comm.(UserEvents).OnUpdatedUser(u)
}
}
@ -140,7 +146,7 @@ func OnUpdatedUser(u *types.User) {
func OnDeletedUser(u *types.User) {
for _, comm := range AllCommunications {
if isType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) {
utils.Log(1, fmt.Sprintf("Sending deleted user notification for user %v", u.Username))
log.Infoln(fmt.Sprintf("Sending deleted user notification for user %v", u.Username))
comm.(UserEvents).OnDeletedUser(u)
}
}
@ -150,7 +156,7 @@ func OnDeletedUser(u *types.User) {
func OnUpdatedCore(c *types.Core) {
for _, comm := range AllCommunications {
if isType(comm, new(CoreEvents)) && isEnabled(comm) && inLimits(comm) {
utils.Log(1, fmt.Sprintf("Sending updated core notification"))
log.Infoln(fmt.Sprintf("Sending updated core notification"))
comm.(CoreEvents).OnUpdatedCore(c)
}
}
@ -178,7 +184,7 @@ func OnNewNotifier(n *Notification) {
func OnUpdatedNotifier(n *Notification) {
for _, comm := range AllCommunications {
if isType(comm, new(NotifierEvents)) && isEnabled(comm) && inLimits(comm) {
utils.Log(1, fmt.Sprintf("Sending updated notifier for %v", n.Id))
log.Infoln(fmt.Sprintf("Sending updated notifier for %v", n.Id))
comm.(NotifierEvents).OnUpdatedNotifier(n)
}
}

View File

@ -233,7 +233,6 @@ func ExampleNotification_OnSuccess() {
example.AddQueue("example", msg)
fmt.Println(len(example.Queue))
// Output:
// INFO: Notifier 'Example' added new item (example) to the queue. (1 queued)
// 1
}
@ -270,7 +269,6 @@ func ExampleNotification_AddQueue() {
queue := example.Queue
fmt.Printf("Example has %v items in the queue", len(queue))
// Output:
// INFO: Notifier 'Example' added new item (example) to the queue. (2 queued)
// Example has 2 items in the queue
}

View File

@ -33,6 +33,7 @@ var (
// db holds the Statping database connection
db *gorm.DB
timezone float32
log = utils.Log.WithField("type", "notifier")
)
// Notification contains all the fields for a Statping Notifier.
@ -102,7 +103,7 @@ func (n *Notification) AfterFind() (err error) {
func (n *Notification) AddQueue(uid string, msg interface{}) {
data := &QueueData{uid, msg}
n.Queue = append(n.Queue, data)
utils.Log(1, fmt.Sprintf("Notifier '%v' added new item (%v) to the queue. (%v queued)", n.Method, uid, len(n.Queue)))
log.WithFields(utils.ToFields(data, n)).Infoln(fmt.Sprintf("Notifier '%v' added new item (%v) to the queue. (%v queued)", n.Method, uid, len(n.Queue)))
}
// CanTest returns true if the notifier implements the OnTest interface
@ -169,8 +170,8 @@ func normalizeType(ty interface{}) string {
func (n *Notification) makeLog(msg interface{}) {
log := &NotificationLog{
Message: normalizeType(msg),
Time: utils.Timestamp(time.Now()),
Timestamp: time.Now(),
Time: utils.Timestamp(utils.Now()),
Timestamp: utils.Now(),
}
n.logs = append(n.logs, log)
}
@ -290,9 +291,9 @@ CheckNotifier:
msg := notification.Queue[0]
err := n.Send(msg.Data)
if err != nil {
utils.Log(2, fmt.Sprintf("Notifier '%v' had an error: %v", notification.Method, err))
log.WithFields(utils.ToFields(notification, msg)).Warnln(fmt.Sprintf("Notifier '%v' had an error: %v", notification.Method, err))
} else {
utils.Log(1, fmt.Sprintf("Notifier '%v' sent outgoing message (%v) %v left in queue.", notification.Method, msg.Id, len(notification.Queue)))
log.WithFields(utils.ToFields(notification, msg)).Infoln(fmt.Sprintf("Notifier '%v' sent outgoing message (%v) %v left in queue.", notification.Method, msg.Id, len(notification.Queue)))
}
notification.makeLog(msg.Data)
if len(notification.Queue) > 1 {
@ -311,10 +312,13 @@ CheckNotifier:
// install will check the database for the notification, if its not inserted it will insert a new record for it
func install(n Notifier) error {
inDb := isInDatabase(n)
log.WithField("installed", inDb).
WithFields(utils.ToFields(n)).
Debugln(fmt.Sprintf("Checking if notifier '%v' is installed: %v", n.Select().Method, inDb))
if !inDb {
_, err := insertDatabase(n)
if err != nil {
utils.Log(3, err)
log.Errorln(err)
return err
}
}
@ -333,13 +337,13 @@ func (n *Notification) LastSent() time.Duration {
// SentLastHour returns the total amount of notifications sent in last 1 hour
func (n *Notification) SentLastHour() int {
since := time.Now().Add(-1 * time.Hour)
since := utils.Now().Add(-1 * time.Hour)
return n.SentLast(since)
}
// SentLastMinute returns the total amount of notifications sent in last 1 minute
func (n *Notification) SentLastMinute() int {
since := time.Now().Add(-1 * time.Minute)
since := utils.Now().Add(-1 * time.Minute)
return n.SentLast(since)
}
@ -475,5 +479,5 @@ var ExampleService = &types.Service{
LastStatusCode: 404,
Expected: types.NewNullString("test example"),
LastResponse: "<html>this is an example response</html>",
CreatedAt: time.Now().Add(-24 * time.Hour),
CreatedAt: utils.Now().Add(-24 * time.Hour),
}

View File

@ -29,7 +29,7 @@ var (
// InsertSampleData will create the example/dummy services for a brand new Statping installation
func InsertSampleData() error {
utils.Log(1, "Inserting Sample Data...")
log.Infoln("Inserting Sample Data...")
insertSampleGroups()
createdOn := time.Now().Add(((-24 * 30) * 3) * time.Hour).UTC()
@ -113,7 +113,7 @@ func InsertSampleData() error {
insertSampleIncidents()
utils.Log(1, "Sample data has finished importing")
log.Infoln("Sample data has finished importing")
return nil
}
@ -211,7 +211,7 @@ func InsertSampleHits() error {
service := SelectService(i)
seed := time.Now().UnixNano()
utils.Log(1, fmt.Sprintf("Adding %v sample hit records to service %v", SampleHits, service.Name))
log.Infoln(fmt.Sprintf("Adding %v sample hit records to service %v", SampleHits, service.Name))
createdAt := sampleStart
p := utils.NewPerlin(2., 2., 10, seed)
@ -455,17 +455,17 @@ func InsertLargeSampleData() error {
func insertFailureRecords(since time.Time, amount int64) {
for i := int64(14); i <= 15; i++ {
service := SelectService(i)
utils.Log(1, fmt.Sprintf("Adding %v Failure records to service %v", amount, service.Name))
log.Infoln(fmt.Sprintf("Adding %v Failure records to service %v", amount, service.Name))
createdAt := since
for fi := int64(1); fi <= amount; fi++ {
createdAt = createdAt.Add(2 * time.Minute)
failure := &Failure{&types.Failure{
failure := &types.Failure{
Service: service.Id,
Issue: "testing right here",
CreatedAt: createdAt,
}}
}
service.CreateFailure(failure)
}
@ -476,7 +476,7 @@ func insertFailureRecords(since time.Time, amount int64) {
func insertHitRecords(since time.Time, amount int64) {
for i := int64(1); i <= 15; i++ {
service := SelectService(i)
utils.Log(1, fmt.Sprintf("Adding %v hit records to service %v", amount, service.Name))
log.Infoln(fmt.Sprintf("Adding %v hit records to service %v", amount, service.Name))
createdAt := since
p := utils.NewPerlin(2, 2, 5, time.Now().UnixNano())
for hi := int64(1); hi <= amount; hi++ {

View File

@ -101,7 +101,7 @@ func (c *Core) SelectAllServices(start bool) ([]*Service, error) {
var services []*Service
db := servicesDB().Find(&services).Order("order_id desc")
if db.Error != nil {
utils.Log(3, fmt.Sprintf("service error: %v", db.Error))
log.Errorln(fmt.Sprintf("service error: %v", db.Error))
return nil, db.Error
}
CoreApp.Services = nil
@ -272,7 +272,7 @@ func GraphDataRaw(service types.ServiceInterface, start, end time.Time, group st
model = model.Order("timeframe asc", false).Group("timeframe")
rows, err := model.Rows()
if err != nil {
utils.Log(3, fmt.Errorf("issue fetching service chart data: %v", err))
log.Errorln(fmt.Errorf("issue fetching service chart data: %v", err))
}
for rows.Next() {
var gd DateScan
@ -284,7 +284,7 @@ func GraphDataRaw(service types.ServiceInterface, start, end time.Time, group st
if CoreApp.DbConnection == "postgres" {
createdTime, err = time.Parse(types.TIME_NANO, createdAt)
if err != nil {
utils.Log(4, fmt.Errorf("issue parsing time from database: %v to %v", createdAt, types.TIME_NANO))
log.Errorln(fmt.Errorf("issue parsing time from database: %v to %v", createdAt, types.TIME_NANO))
}
} else {
createdTime, err = time.Parse(types.TIME, createdAt)
@ -301,7 +301,7 @@ func GraphDataRaw(service types.ServiceInterface, start, end time.Time, group st
func (d *DateScanObj) ToString() string {
data, err := json.Marshal(d.Array)
if err != nil {
utils.Log(2, err)
log.Warnln(err)
return "{}"
}
return string(data)
@ -371,7 +371,7 @@ func (s *Service) Delete() error {
i := s.index()
err := servicesDB().Delete(s)
if err.Error != nil {
utils.Log(3, fmt.Sprintf("Failed to delete service %v. %v", s.Name, err.Error))
log.Errorln(fmt.Sprintf("Failed to delete service %v. %v", s.Name, err.Error))
return err.Error
}
s.Close()
@ -386,7 +386,7 @@ func (s *Service) Delete() error {
func (s *Service) Update(restart bool) error {
err := servicesDB().Update(&s)
if err.Error != nil {
utils.Log(3, fmt.Sprintf("Failed to update service %v. %v", s.Name, err))
log.Errorln(fmt.Sprintf("Failed to update service %v. %v", s.Name, err))
return err.Error
}
// clear the notification queue for a service
@ -413,7 +413,7 @@ func (s *Service) Create(check bool) (int64, error) {
s.CreatedAt = time.Now()
db := servicesDB().Create(s)
if db.Error != nil {
utils.Log(3, fmt.Sprintf("Failed to create service %v #%v: %v", s.Name, s.Id, db.Error))
log.Errorln(fmt.Sprintf("Failed to create service %v #%v: %v", s.Name, s.Id, db.Error))
return 0, db.Error
}
s.Start()

View File

@ -17,6 +17,7 @@ package core
import (
"github.com/hunterlong/statping/types"
"github.com/hunterlong/statping/utils"
"github.com/stretchr/testify/assert"
"testing"
"time"
@ -24,7 +25,6 @@ import (
var (
newServiceId int64
newGroupId int64
)
func TestSelectHTTPService(t *testing.T) {
@ -118,7 +118,7 @@ func TestCheckTCPService(t *testing.T) {
}
func TestServiceOnline24Hours(t *testing.T) {
since := time.Now().Add(-24 * time.Hour).Add(-10 * time.Minute)
since := utils.Now().Add(-24 * time.Hour).Add(-10 * time.Minute)
service := SelectService(1)
assert.Equal(t, float32(100), service.OnlineSince(since))
service2 := SelectService(5)
@ -134,7 +134,7 @@ func TestServiceSmallText(t *testing.T) {
}
func TestServiceAvgUptime(t *testing.T) {
since := time.Now().Add(-24 * time.Hour).Add(-10 * time.Minute)
since := utils.Now().Add(-24 * time.Hour).Add(-10 * time.Minute)
service := SelectService(1)
assert.NotEqual(t, "0.00", service.AvgUptime(since))
service2 := SelectService(5)
@ -256,10 +256,10 @@ func TestServiceFailedTCPCheck(t *testing.T) {
}
func TestCreateServiceFailure(t *testing.T) {
fail := &Failure{&types.Failure{
fail := &types.Failure{
Issue: "This is not an issue, but it would container HTTP response errors.",
Method: "http",
}}
}
service := SelectService(8)
id, err := service.CreateFailure(fail)
assert.Nil(t, err)
@ -398,7 +398,7 @@ func TestService_TotalFailures24(t *testing.T) {
func TestService_TotalFailuresOnDate(t *testing.T) {
t.SkipNow()
ago := time.Now().UTC()
ago := utils.Now().UTC()
service := SelectService(8)
failures, err := service.TotalFailuresOnDate(ago)
assert.Nil(t, err)

View File

@ -77,7 +77,7 @@ func (u *User) Create() (int64, error) {
return 0, db.Error
}
if u.Id == 0 {
utils.Log(3, fmt.Sprintf("Failed to create User %v. %v", u.Username, db.Error))
log.Errorln(fmt.Sprintf("Failed to create User %v. %v", u.Username, db.Error))
return 0, db.Error
}
return u.Id, db.Error
@ -88,7 +88,7 @@ func SelectAllUsers() ([]*User, error) {
var users []*User
db := usersDB().Find(&users)
if db.Error != nil {
utils.Log(3, fmt.Sprintf("Failed to load all users. %v", db.Error))
log.Errorln(fmt.Sprintf("Failed to load all users. %v", db.Error))
return nil, db.Error
}
return users, db.Error
@ -99,7 +99,7 @@ func SelectAllUsers() ([]*User, error) {
func AuthUser(username, password string) (*User, bool) {
user, err := SelectUsername(username)
if err != nil {
utils.Log(2, fmt.Errorf("user %v not found", username))
log.Warnln(fmt.Errorf("user %v not found", username))
return nil, false
}
if CheckHash(password, user.Password) {

240
dev/COVERAGE.html vendored

File diff suppressed because one or more lines are too long

4
dev/Dockerfile vendored
View File

@ -1,5 +1,5 @@
FROM golang:1.11-alpine as base
MAINTAINER "Hunter Long (https://github.com/hunterlong)"
FROM golang:1.13-alpine as base
LABEL maintainer="Hunter Long (https://github.com/hunterlong)"
ARG VERSION
ENV DEP_VERSION v0.5.0
RUN apk add --no-cache libstdc++ gcc g++ make git ca-certificates linux-headers wget curl jq

2
dev/README.md vendored
View File

@ -2496,7 +2496,7 @@ for application logging
```go
func Log(level int, err interface{}) error
```
Log creates a new entry in the Logger. Log has 1-5 levels depending on how
Log creates a new entry in the utils.Log. Log has 1-5 levels depending on how
critical the log/error is
#### func NewSHA1Hash

4
doc.go
View File

@ -1,4 +1,4 @@
// Package statping is a server monitoring application that includs a status page server. Visit the Statping repo at
// Package statping is a server monitoring application that includes a status page server. Visit the Statping repo at
// https://github.com/hunterlong/statping to get a full understanding of what this application can do.
//
// Install Statping
@ -12,7 +12,7 @@
// brew install statping
//
// // Linux installation
// bash <(curl -s https://assets.statping.com/install.sh)
// curl -o- -L https://statping.com/install.sh | bash
// statping version
//
// Docker

2
go.mod
View File

@ -8,6 +8,7 @@ require (
github.com/agnivade/levenshtein v1.0.2 // indirect
github.com/ararog/timeago v0.0.0-20160328174124-e9969cf18b8d
github.com/daaku/go.zipexe v1.0.1 // indirect
github.com/fatih/structs v1.1.0
github.com/go-mail/mail v2.3.1+incompatible
github.com/go-yaml/yaml v2.1.0+incompatible
github.com/gorilla/mux v1.7.3
@ -20,6 +21,7 @@ require (
github.com/rendon/testcli v0.0.0-20161027181003-6283090d169f
github.com/russross/blackfriday/v2 v2.0.1
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/sirupsen/logrus v1.2.0
github.com/stretchr/testify v1.4.0
github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e
github.com/vektah/gqlparser v1.1.2

4
go.sum
View File

@ -39,6 +39,8 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
@ -95,6 +97,7 @@ github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqx
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
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/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
@ -144,6 +147,7 @@ github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJ
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
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=

View File

@ -58,7 +58,7 @@ func apiClearCacheHandler(w http.ResponseWriter, r *http.Request) {
}
func sendErrorJson(err error, w http.ResponseWriter, r *http.Request) {
utils.Log(2, fmt.Errorf("sending error response for %v: %v", r.URL.String(), err.Error()))
log.Warnln(fmt.Errorf("sending error response for %v: %v", r.URL.String(), err.Error()))
output := apiResponse{
Status: "error",
Error: err.Error(),

View File

@ -1,6 +1,7 @@
package handlers
import (
"github.com/hunterlong/statping/utils"
"sync"
"time"
)
@ -25,7 +26,7 @@ func (item Item) Expired() bool {
if item.Expiration == 0 {
return false
}
return time.Now().UnixNano() > item.Expiration
return utils.Now().UnixNano() > item.Expiration
}
//Storage mecanism for caching strings in memory
@ -68,6 +69,6 @@ func (s Storage) Set(key string, content []byte, duration time.Duration) {
defer s.mu.Unlock()
s.items[key] = Item{
Content: content,
Expiration: time.Now().Add(duration).UnixNano(),
Expiration: utils.Now().Add(duration).UnixNano(),
}
}

View File

@ -24,7 +24,6 @@ import (
"github.com/hunterlong/statping/utils"
"net"
"net/http"
"time"
)
func apiAllCheckinsHandler(w http.ResponseWriter, r *http.Request) {
@ -81,7 +80,7 @@ func checkinHitHandler(w http.ResponseWriter, r *http.Request) {
hit := &types.CheckinHit{
Checkin: checkin.Id,
From: ip,
CreatedAt: time.Now().UTC(),
CreatedAt: utils.Now().UTC(),
}
checkinHit := core.ReturnCheckinHit(hit)
if checkin.Last() == nil {
@ -95,7 +94,7 @@ func checkinHitHandler(w http.ResponseWriter, r *http.Request) {
return
}
checkin.Failing = false
checkin.LastHit = utils.Timezoner(time.Now().UTC(), core.CoreApp.Timezone)
checkin.LastHit = utils.Timezoner(utils.Now().UTC(), core.CoreApp.Timezone)
sendJsonAction(checkinHit, "update", w, r)
}

View File

@ -24,7 +24,6 @@ import (
"github.com/hunterlong/statping/utils"
"net/http"
"strconv"
"time"
)
func dashboardHandler(w http.ResponseWriter, r *http.Request) {
@ -50,7 +49,7 @@ func loginHandler(w http.ResponseWriter, r *http.Request) {
session.Values["user_id"] = user.Id
session.Values["admin"] = user.Admin.Bool
session.Save(r, w)
utils.Log(1, fmt.Sprintf("User %v logged in from IP %v", user.Username, r.RemoteAddr))
log.Infoln(fmt.Sprintf("User %v logged in from IP %v", user.Username, r.RemoteAddr))
http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
} else {
err := core.ErrorResponse{Error: "Incorrect login information submitted, try again."}
@ -115,6 +114,6 @@ func exportHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Length", strconv.Itoa(fileSize))
w.Header().Set("Content-Control", "private, no-transform, no-store, must-revalidate")
http.ServeContent(w, r, "export.json", time.Now(), bytes.NewReader(export))
http.ServeContent(w, r, "export.json", utils.Now(), bytes.NewReader(export))
}

View File

@ -56,11 +56,11 @@ func RunHTTPServer(ip string, port int) error {
cert := utils.FileExists(utils.Directory + "/server.crt")
if key && cert {
utils.Log(1, "server.cert and server.key was found in root directory! Starting in SSL mode.")
utils.Log(1, fmt.Sprintf("Statping Secure HTTPS Server running on https://%v:%v", ip, 443))
log.Infoln("server.cert and server.key was found in root directory! Starting in SSL mode.")
log.Infoln(fmt.Sprintf("Statping Secure HTTPS Server running on https://%v:%v", ip, 443))
usingSSL = true
} else {
utils.Log(1, "Statping HTTP Server running on http://"+host)
log.Infoln("Statping HTTP Server running on http://" + host)
}
router = Router()
@ -178,7 +178,7 @@ func loadTemplate(w http.ResponseWriter, r *http.Request) error {
mainTemplate.Funcs(handlerFuncs(w, r))
mainTemplate, err = mainTemplate.Parse(mainTmpl)
if err != nil {
utils.Log(4, err)
log.Errorln(err)
return err
}
// render all templates
@ -187,7 +187,7 @@ func loadTemplate(w http.ResponseWriter, r *http.Request) error {
tmp, _ := source.TmplBox.String(temp)
mainTemplate, err = mainTemplate.Parse(tmp)
if err != nil {
utils.Log(4, err)
log.Errorln(err)
return err
}
}
@ -196,7 +196,7 @@ func loadTemplate(w http.ResponseWriter, r *http.Request) error {
tmp, _ := source.JsBox.String(temp)
mainTemplate, err = mainTemplate.Parse(tmp)
if err != nil {
utils.Log(4, err)
log.Errorln(err)
return err
}
}
@ -205,7 +205,6 @@ func loadTemplate(w http.ResponseWriter, r *http.Request) error {
// ExecuteResponse will render a HTTP response for the front end user
func ExecuteResponse(w http.ResponseWriter, r *http.Request, file string, data interface{}, redirect interface{}) {
utils.Http(r)
if url, ok := redirect.(string); ok {
http.Redirect(w, r, url, http.StatusSeeOther)
return
@ -216,15 +215,15 @@ func ExecuteResponse(w http.ResponseWriter, r *http.Request, file string, data i
loadTemplate(w, r)
render, err := source.TmplBox.String(file)
if err != nil {
utils.Log(4, err)
log.Errorln(err)
}
// render the page requested
if _, err := mainTemplate.Parse(render); err != nil {
utils.Log(4, err)
log.Errorln(err)
}
// execute the template
if err := mainTemplate.Execute(w, data); err != nil {
utils.Log(4, err)
log.Errorln(err)
}
}
@ -232,7 +231,7 @@ func ExecuteResponse(w http.ResponseWriter, r *http.Request, file string, data i
func executeJSResponse(w http.ResponseWriter, r *http.Request, file string, data interface{}) {
render, err := source.JsBox.String(file)
if err != nil {
utils.Log(4, err)
log.Errorln(err)
}
if usingSSL {
w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
@ -247,10 +246,10 @@ func executeJSResponse(w http.ResponseWriter, r *http.Request, file string, data
},
})
if _, err := t.Parse(render); err != nil {
utils.Log(4, err)
log.Errorln(err)
}
if err := t.Execute(w, data); err != nil {
utils.Log(4, err)
log.Errorln(err)
}
}

View File

@ -1,15 +1,31 @@
package handlers
import (
"fmt"
"github.com/hunterlong/statping/core"
"github.com/hunterlong/statping/utils"
"net/http"
"net/http/httptest"
"time"
)
// sendLog is a http middleware that will log the duration of request and other useful fields
func sendLog(handler func(w http.ResponseWriter, r *http.Request)) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t1 := utils.Now()
handler(w, r)
t2 := utils.Now().Sub(t1)
log.WithFields(utils.ToFields(w, r)).
WithField("url", r.RequestURI).
WithField("method", r.Method).
WithField("load_micro_seconds", t2.Microseconds()).
Infoln(fmt.Sprintf("%v (%v) | IP: %v", r.RequestURI, r.Method, r.Host))
})
}
// authenticated is a middleware function to check if user is an Admin before running original request
func authenticated(handler func(w http.ResponseWriter, r *http.Request), redirect bool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
return sendLog(func(w http.ResponseWriter, r *http.Request) {
if !IsFullAuthenticated(r) {
if redirect {
http.Redirect(w, r, "/", http.StatusSeeOther)
@ -24,7 +40,7 @@ func authenticated(handler func(w http.ResponseWriter, r *http.Request), redirec
// readOnly is a middleware function to check if user is a User before running original request
func readOnly(handler func(w http.ResponseWriter, r *http.Request), redirect bool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
return sendLog(func(w http.ResponseWriter, r *http.Request) {
if !IsReadAuthenticated(r) {
if redirect {
http.Redirect(w, r, "/", http.StatusSeeOther)
@ -39,7 +55,7 @@ func readOnly(handler func(w http.ResponseWriter, r *http.Request), redirect boo
// cached is a middleware function that accepts a duration and content type and will cache the response of the original request
func cached(duration, contentType string, handler func(w http.ResponseWriter, r *http.Request)) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
return sendLog(func(w http.ResponseWriter, r *http.Request) {
content := CacheStorage.Get(r.RequestURI)
w.Header().Set("Content-Type", contentType)
w.Header().Set("Access-Control-Allow-Origin", "*")

View File

@ -85,7 +85,7 @@ func testNotificationHandler(w http.ResponseWriter, r *http.Request) {
fakeNotifer, notif, err := notifier.SelectNotifier(method)
if err != nil {
utils.Log(3, fmt.Sprintf("issue saving notifier %v: %v", method, err))
log.Errorln(fmt.Sprintf("issue saving notifier %v: %v", method, err))
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
return
}

View File

@ -25,11 +25,11 @@ import (
"github.com/hunterlong/statping/source"
"github.com/hunterlong/statping/utils"
"net/http"
"time"
)
var (
router *mux.Router
log = utils.Log.WithField("type", "handlers")
)
// Router returns all of the routes used in Statping.
@ -58,9 +58,9 @@ func Router() *mux.Router {
r.Handle("/charts.js", http.HandlerFunc(renderServiceChartsHandler))
r.Handle("/setup", http.HandlerFunc(setupHandler)).Methods("GET")
r.Handle("/setup", http.HandlerFunc(processSetupHandler)).Methods("POST")
r.Handle("/dashboard", http.HandlerFunc(dashboardHandler)).Methods("GET")
r.Handle("/dashboard", http.HandlerFunc(loginHandler)).Methods("POST")
r.Handle("/logout", http.HandlerFunc(logoutHandler))
r.Handle("/dashboard", sendLog(dashboardHandler)).Methods("GET")
r.Handle("/dashboard", sendLog(loginHandler)).Methods("POST")
r.Handle("/logout", sendLog(logoutHandler))
r.Handle("/plugins/download/{name}", authenticated(pluginsDownloadHandler, true))
r.Handle("/plugins/{name}/save", authenticated(pluginSavedHandler, true)).Methods("POST")
r.Handle("/help", authenticated(helpHandler, true))
@ -90,11 +90,11 @@ func Router() *mux.Router {
// SERVICE Routes
r.Handle("/services", authenticated(servicesHandler, true)).Methods("GET")
r.Handle("/service/create", authenticated(createServiceHandler, true)).Methods("GET")
r.Handle("/service/{id}", http.HandlerFunc(servicesViewHandler)).Methods("GET")
r.Handle("/service/{id}", sendLog(servicesViewHandler)).Methods("GET")
r.Handle("/service/{id}/edit", authenticated(servicesViewHandler, true)).Methods("GET")
r.Handle("/service/{id}/delete_failures", authenticated(servicesDeleteFailuresHandler, true)).Methods("GET")
r.Handle("/group/{id}", http.HandlerFunc(groupViewHandler)).Methods("GET")
r.Handle("/group/{id}", sendLog(groupViewHandler)).Methods("GET")
// API GROUPS Routes
r.Handle("/api/groups", readOnly(apiAllGroupHandler, false)).Methods("GET")
@ -115,9 +115,9 @@ func Router() *mux.Router {
r.Handle("/api/services/{id}", readOnly(apiServiceHandler, false)).Methods("GET")
r.Handle("/api/reorder/services", authenticated(reorderServiceHandler, false)).Methods("POST")
r.Handle("/api/services/{id}/running", authenticated(apiServiceRunningHandler, false)).Methods("POST")
r.Handle("/api/services/{id}/data", cached("30s", "application/json", http.HandlerFunc(apiServiceDataHandler))).Methods("GET")
r.Handle("/api/services/{id}/ping", cached("30s", "application/json", http.HandlerFunc(apiServicePingDataHandler))).Methods("GET")
r.Handle("/api/services/{id}/heatmap", cached("30s", "application/json", http.HandlerFunc(apiServiceHeatmapHandler))).Methods("GET")
r.Handle("/api/services/{id}/data", cached("30s", "application/json", apiServiceDataHandler)).Methods("GET")
r.Handle("/api/services/{id}/ping", cached("30s", "application/json", apiServicePingDataHandler)).Methods("GET")
r.Handle("/api/services/{id}/heatmap", cached("30s", "application/json", apiServiceHeatmapHandler)).Methods("GET")
r.Handle("/api/services/{id}", authenticated(apiServiceUpdateHandler, false)).Methods("POST")
r.Handle("/api/services/{id}", authenticated(apiServiceDeleteHandler, false)).Methods("DELETE")
r.Handle("/api/services/{id}/failures", authenticated(apiServiceFailuresHandler, false)).Methods("GET")
@ -152,7 +152,7 @@ func Router() *mux.Router {
r.Handle("/api/checkin/{api}", authenticated(apiCheckinHandler, false)).Methods("GET")
r.Handle("/api/checkin", authenticated(checkinCreateHandler, false)).Methods("POST")
r.Handle("/api/checkin/{api}", authenticated(checkinDeleteHandler, false)).Methods("DELETE")
r.Handle("/checkin/{api}", http.HandlerFunc(checkinHitHandler))
r.Handle("/checkin/{api}", sendLog(checkinHitHandler))
// Static Files Routes
r.PathPrefix("/files/postman.json").Handler(http.StripPrefix("/files/", http.FileServer(source.TmplBox.HTTPBox())))
@ -161,10 +161,10 @@ func Router() *mux.Router {
// API Generic Routes
r.Handle("/metrics", readOnly(prometheusHandler, false))
r.Handle("/health", http.HandlerFunc(healthCheckHandler))
r.Handle("/health", sendLog(healthCheckHandler))
r.Handle("/.well-known/", http.StripPrefix("/.well-known/", http.FileServer(http.Dir(dir+"/.well-known"))))
r.NotFoundHandler = http.HandlerFunc(error404Handler)
r.NotFoundHandler = sendLog(error404Handler)
return r
}
@ -175,7 +175,7 @@ func resetRouter() {
func resetCookies() {
if core.CoreApp != nil {
cookie := fmt.Sprintf("%v_%v", core.CoreApp.ApiSecret, time.Now().Nanosecond())
cookie := fmt.Sprintf("%v_%v", core.CoreApp.ApiSecret, utils.Now().Nanosecond())
sessionStore = sessions.NewCookieStore([]byte(cookie))
} else {
sessionStore = sessions.NewCookieStore([]byte("secretinfo"))

View File

@ -33,8 +33,8 @@ func renderServiceChartsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/javascript")
w.Header().Set("Cache-Control", "max-age=60")
end := time.Now().UTC()
start := time.Now().Add((-24 * 7) * time.Hour).UTC()
end := utils.Now().UTC()
start := utils.Now().Add((-24 * 7) * time.Hour).UTC()
var srvs []*core.Service
for _, s := range services {
srvs = append(srvs, s.(*core.Service))
@ -94,7 +94,7 @@ func servicesViewHandler(w http.ResponseWriter, r *http.Request) {
endField := utils.ToInt(fields.Get("end"))
group := r.Form.Get("group")
end := time.Now().UTC()
end := utils.Now().UTC()
start := end.Add((-24 * 7) * time.Hour).UTC()
if startField != 0 {
@ -243,7 +243,7 @@ func apiServiceHeatmapHandler(w http.ResponseWriter, r *http.Request) {
var monthOutput []*dataXyMonth
start := service.CreatedAt
//now := time.Now()
//now := utils.Now()
sY, sM, _ := start.Date()
@ -252,10 +252,10 @@ func apiServiceHeatmapHandler(w http.ResponseWriter, r *http.Request) {
month := int(sM)
maxMonth := 12
for year := int(sY); year <= time.Now().Year(); year++ {
for year := int(sY); year <= utils.Now().Year(); year++ {
if year == time.Now().Year() {
maxMonth = int(time.Now().Month())
if year == utils.Now().Year() {
maxMonth = int(utils.Now().Month())
}
for m := month; m <= maxMonth; m++ {

View File

@ -67,10 +67,9 @@ func saveSettingsHandler(w http.ResponseWriter, r *http.Request) {
app.UseCdn = types.NewNullBool(form.Get("enable_cdn") == "on")
core.CoreApp, err = core.UpdateCore(app)
if err != nil {
utils.Log(3, fmt.Sprintf("issue updating Core: %v", err.Error()))
log.Errorln(fmt.Sprintf("issue updating Core: %v", err.Error()))
}
//notifiers.OnSettingsSaved(core.CoreApp.ToCore())
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
}
@ -91,13 +90,13 @@ func saveSASSHandler(w http.ResponseWriter, r *http.Request) {
func saveAssetsHandler(w http.ResponseWriter, r *http.Request) {
dir := utils.Directory
if err := source.CreateAllAssets(dir); err != nil {
utils.Log(3, err)
log.Errorln(err)
sendErrorJson(err, w, r)
return
}
if err := source.CompileSASS(dir); err != nil {
source.CopyToPublic(source.CssBox, dir+"/assets/css", "base.css")
utils.Log(3, "Default 'base.css' was inserted because SASS did not work.")
log.Errorln("Default 'base.css' was inserted because SASS did not work.")
}
resetRouter()
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
@ -105,7 +104,7 @@ func saveAssetsHandler(w http.ResponseWriter, r *http.Request) {
func deleteAssetsHandler(w http.ResponseWriter, r *http.Request) {
if err := source.DeleteAllAssets(utils.Directory); err != nil {
utils.Log(3, fmt.Errorf("error deleting all assets %v", err))
log.Errorln(fmt.Errorf("error deleting all assets %v", err))
}
resetRouter()
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")
@ -115,7 +114,7 @@ func bulkImportHandler(w http.ResponseWriter, r *http.Request) {
var fileData bytes.Buffer
file, _, err := r.FormFile("file")
if err != nil {
utils.Log(3, fmt.Errorf("error bulk import services: %v", err))
log.Errorln(fmt.Errorf("error bulk import services: %v", err))
w.Write([]byte(err.Error()))
return
}
@ -129,17 +128,17 @@ func bulkImportHandler(w http.ResponseWriter, r *http.Request) {
newService, err := commaToService(col)
if err != nil {
utils.Log(3, fmt.Errorf("issue with row %v: %v", i, err))
log.Errorln(fmt.Errorf("issue with row %v: %v", i, err))
continue
}
service := core.ReturnService(newService)
_, err = service.Create(true)
if err != nil {
utils.Log(3, fmt.Errorf("cannot create service %v: %v", col[0], err))
log.Errorln(fmt.Errorf("cannot create service %v: %v", col[0], err))
continue
}
utils.Log(1, fmt.Sprintf("Created new service %v", service.Name))
log.Infoln(fmt.Sprintf("Created new service %v", service.Name))
}
ExecuteResponse(w, r, "settings.gohtml", core.CoreApp, "/settings")

View File

@ -57,7 +57,7 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
domain := r.PostForm.Get("domain")
email := r.PostForm.Get("email")
sample := r.PostForm.Get("sample_data") == "on"
utils.Log(2, sample)
log.Warnln(sample)
dir := utils.Directory
config := &core.DbConfig{
@ -78,21 +78,21 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
}
if core.Configs, err = config.Save(); err != nil {
utils.Log(4, err)
log.Errorln(err)
config.Error = err
setupResponseError(w, r, config)
return
}
if core.Configs, err = core.LoadConfigFile(dir); err != nil {
utils.Log(3, err)
log.Errorln(err)
config.Error = err
setupResponseError(w, r, config)
return
}
if err = core.Configs.Connect(false, dir); err != nil {
utils.Log(4, err)
log.Errorln(err)
core.DeleteConfig()
config.Error = err
setupResponseError(w, r, config)
@ -104,7 +104,7 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
core.CoreApp, err = config.InsertCore()
if err != nil {
utils.Log(4, err)
log.Errorln(err)
config.Error = err
setupResponseError(w, r, config)
return

View File

@ -1,5 +0,0 @@
build:
docker:
web: Dockerfile
run:
web: statping -port $PORT

167
install.sh Executable file
View File

@ -0,0 +1,167 @@
#!/bin/sh
# Statping installation script for Linux, Mac, and maybe Windows.
#
# This installation script is a modification of Yarn's installation
set -e
reset="\033[0m"
red="\033[31m"
green="\033[32m"
yellow="\033[33m"
cyan="\033[36m"
white="\033[37m"
gpg_key=64B9C6AAE2D55278
gpgurl=https://statping.com/statping.gpg
repo=https://github.com/hunterlong/statping
statping_get_tarball() {
url="https://github.com/hunterlong/statping/releases/latest/download/statping-$1-$2.tar.gz"
printf "$cyan> Downloading latest version for $OS $ARCH...\n$url $reset\n"
# Get both the tarball and its GPG signature
tarball_tmp=`mktemp -t statping.tar.gz.XXXXXXXXXX`
if curl --fail -L -o "$tarball_tmp" "$url"; then
# All this dance is because `tar --strip=1` does not work everywhere
temp=$(mktemp -d statping.XXXXXXXXXX)
tar xzf $tarball_tmp -C "$temp"
statping_verify_integrity "$temp"/statping
printf "$green> Installing to $DEST/statping\n"
mv "$temp"/statping "$DEST"
newversion=`$DEST/statping version`
rm -rf "$temp"
rm $tarball_tmp*
printf "$cyan> Statping is now installed! $reset\n"
printf "$white> Version: $newversion $reset\n"
printf "$white> Repo: $repo $reset\n"
printf "$white> Wiki: $repo/wiki $reset\n"
printf "$white> Issues: $repo/issues $reset\n"
printf "$cyan> Try to run \"statping help\" $reset\n"
else
printf "$red> Failed to download $url.$reset\n"
exit 1;
fi
}
# Verifies the GPG signature of the tarball
statping_verify_integrity() {
# Check if GPG is installed
if [[ -z "$(command -v gpg)" ]]; then
printf "$yellow> WARNING: GPG is not installed, integrity can not be verified!$reset\n"
return
fi
if [ "$statping_GPG" == "no" ]; then
printf "$cyan> WARNING: Skipping GPG integrity check!$reset\n"
return
fi
printf "$cyan> Verifying integrity with gpg key from $gpgurl...$reset\n"
# Grab the public key if it doesn't already exist
gpg --list-keys $gpg_key >/dev/null 2>&1 || (curl -sS $gpgurl | gpg --import)
if [ ! -f "$1.asc" ]; then
printf "$red> Could not download GPG signature for this Statping release. This means the release can not be verified!$reset\n"
statping_verify_or_quit "> Do you really want to continue?"
return
fi
# Actually perform the verification
if gpg --verify "$1.asc" $1; then
printf "$green> GPG signature looks good$reset\n"
else
printf "$red> GPG signature for this Statping release is invalid! This is BAD and may mean the release has been tampered with. It is strongly recommended that you report this to the Statping developers.$reset\n"
statping_verify_or_quit "> Do you really want to continue?"
fi
}
statping_reset() {
unset -f statping_install statping_reset statping_get_tarball statping_verify_integrity statping_verify_or_quit statping_brew_install getOS getArch
}
statping_brew_install() {
if [[ -z "$(command -v brew --version)" ]]; then
printf "${white}Using Brew to install!$reset\n"
printf "${yellow}brew tap hunterlong/statping$reset\n"
brew tap hunterlong/statping
brew install statping
printf "${green}Brew installation is complete!$reset\n"
printf "${yellow}You can use 'brew upgrade' to upgrade Statping next time.$reset\n"
else
statping_get_tarball $OS $ARCH
fi
}
statping_install() {
printf "${white}Installing Statping!$reset\n"
getOS
getArch
if [ -d "$DEST" ]; then
if ! loc="$(type -p "statping version")" || [[ -z $loc ]]; then
specified_version=`curl -sS https://raw.githubusercontent.com/hunterlong/statping/master/version.txt`
statping_version=`statping version`
if [ "$specified_version" = "$statping_version" ]; then
printf "$green> Statping is already at the $specified_version version.$reset\n"
exit 0
fi
fi
fi
if [ "$OS" == "osx" ]; then
statping_brew_install
else
statping_get_tarball $OS $ARCH
fi
statping_reset
}
statping_verify_or_quit() {
read -p "$1 [y/N] " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]
then
printf "$red> Aborting$reset\n"
exit 1
fi
}
# get the users operating system
getOS() {
OS="`uname`"
case $OS in
'Linux')
OS='linux'
DEST=/usr/local/bin
alias ls='ls --color=auto'
;;
'FreeBSD')
OS='freebsd'
DEST=/usr/local/bin
alias ls='ls -G'
;;
'WindowsNT')
OS='windows'
DEST=/usr/local/bin
;;
'Darwin')
OS='osx'
DEST=/usr/local/bin
;;
'SunOS')
OS='solaris'
DEST=/usr/local/bin
;;
'AIX') ;;
*) ;;
esac
}
# get 64x or 32 machine arch
getArch() {
MACHINE_TYPE=`uname -m`
if [ ${MACHINE_TYPE} == 'x86_64' ]; then
ARCH="x64"
else
ARCH="x32"
fi
}
cd ~
statping_install $1 $2

View File

@ -89,8 +89,8 @@ func (u *commandLine) OnSave() error {
// OnTest for commandLine triggers when this notifier has been saved
func (u *commandLine) OnTest() error {
in, out, err := runCommand(u.Host, u.Var1)
utils.Log(1, in)
utils.Log(1, out)
utils.Log.Infoln(in)
utils.Log.Infoln(out)
return err
}

View File

@ -216,7 +216,7 @@ func (u *email) Select() *notifier.Notification {
// OnSave triggers when this notifier has been saved
func (u *email) OnSave() error {
utils.Log(1, fmt.Sprintf("Notification %v is receiving updated information.", u.Method))
utils.Log.Infoln(fmt.Sprintf("Notification %v is receiving updated information.", u.Method))
// Do updating stuff here
return nil
}
@ -235,7 +235,7 @@ func (u *email) OnTest() error {
LastStatusCode: 200,
Expected: types.NewNullString("test example"),
LastResponse: "<html>this is an example response</html>",
CreatedAt: time.Now().Add(-24 * time.Hour),
CreatedAt: utils.Now().Add(-24 * time.Hour),
}
email := &emailOutgoing{
To: u.Var2,
@ -262,7 +262,7 @@ func (u *email) dialSend(email *emailOutgoing) error {
m.SetHeader("Subject", email.Subject)
m.SetBody("text/html", email.Source)
if err := mailer.DialAndSend(m); err != nil {
utils.Log(3, fmt.Sprintf("email '%v' sent to: %v (size: %v) %v", email.Subject, email.To, len([]byte(email.Source)), err))
utils.Log.Errorln(fmt.Sprintf("email '%v' sent to: %v (size: %v) %v", email.Subject, email.To, len([]byte(email.Source)), err))
return err
}
return nil
@ -277,11 +277,11 @@ func emailTemplate(contents string, data interface{}) string {
t := template.New("email")
t, err := t.Parse(contents)
if err != nil {
utils.Log(3, err)
utils.Log.Errorln(err)
}
var tpl bytes.Buffer
if err := t.Execute(&tpl, data); err != nil {
utils.Log(2, err)
utils.Log.Warnln(err)
}
result := tpl.String()
return result

View File

@ -85,7 +85,7 @@ func (u *lineNotifier) OnSuccess(s *types.Service) {
// OnSave triggers when this notifier has been saved
func (u *lineNotifier) OnSave() error {
msg := fmt.Sprintf("Notification %v is receiving updated information.", u.Method)
utils.Log(1, msg)
utils.Log.Infoln(msg)
u.AddQueue("saved", msg)
return nil
}

View File

@ -44,13 +44,13 @@ var TestService = &types.Service{
LastStatusCode: 404,
Online: true,
LastResponse: "<html>this is an example response</html>",
CreatedAt: time.Now().Add(-24 * time.Hour),
CreatedAt: utils.Now().Add(-24 * time.Hour),
}
var TestFailure = &types.Failure{
Issue: "testing",
Service: 1,
CreatedAt: time.Now().Add(-12 * time.Hour),
CreatedAt: utils.Now().Add(-12 * time.Hour),
}
var TestUser = &types.User{

View File

@ -99,7 +99,7 @@ func (u *slack) OnFailure(s *types.Service, f *types.Failure) {
message := slackMessage{
Service: s,
Template: failingTemplate,
Time: time.Now().Unix(),
Time: utils.Now().Unix(),
Issue: f.Issue,
}
parseSlackMessage(s.Id, failingTemplate, message)
@ -112,7 +112,7 @@ func (u *slack) OnSuccess(s *types.Service) {
message := slackMessage{
Service: s,
Template: successTemplate,
Time: time.Now().Unix(),
Time: utils.Now().Unix(),
}
parseSlackMessage(s.Id, successTemplate, message)
}

View File

@ -103,7 +103,7 @@ func (u *telegram) OnSuccess(s *types.Service) {
// OnSave triggers when this notifier has been saved
func (u *telegram) OnSave() error {
utils.Log(1, fmt.Sprintf("Notification %v is receiving updated information.", u.Method))
utils.Log.Infoln(fmt.Sprintf("Notification %v is receiving updated information.", u.Method))
// Do updating stuff here

View File

@ -113,7 +113,7 @@ func (u *twilio) OnSuccess(s *types.Service) {
// OnSave triggers when this notifier has been saved
func (u *twilio) OnSave() error {
utils.Log(1, fmt.Sprintf("Notification %v is receiving updated information.", u.Method))
utils.Log.Infoln(fmt.Sprintf("Notification %v is receiving updated information.", u.Method))
// Do updating stuff here

View File

@ -98,7 +98,7 @@ func replaceBodyText(body string, s *types.Service, f *types.Failure) string {
}
func (w *webhooker) sendHttpWebhook(body string) (*http.Response, error) {
utils.Log(1, fmt.Sprintf("sending body: '%v' to %v as a %v request", body, w.Host, w.Var1))
utils.Log.Infoln(fmt.Sprintf("sending body: '%v' to %v as a %v request", body, w.Host, w.Var1))
client := new(http.Client)
client.Timeout = time.Duration(10 * time.Second)
var buf *bytes.Buffer
@ -136,7 +136,7 @@ func (w *webhooker) OnTest() error {
}
defer resp.Body.Close()
content, err := ioutil.ReadAll(resp.Body)
utils.Log(1, fmt.Sprintf("Webhook notifier received: '%v'", string(content)))
utils.Log.Infoln(fmt.Sprintf("Webhook notifier received: '%v'", string(content)))
return err
}

View File

@ -41,6 +41,7 @@ import (
var (
AllPlugins []*types.PluginObject
dir string
log = utils.Log.WithField("type", "plugin")
)
func init() {
@ -49,7 +50,7 @@ func init() {
}
func LoadPlugin(file string) error {
utils.Log(1, fmt.Sprintf("opening file %v", file))
log.Infoln(fmt.Sprintf("opening file %v", file))
f, err := os.Open(file)
if err != nil {
return err
@ -58,29 +59,29 @@ func LoadPlugin(file string) error {
fSplit := strings.Split(f.Name(), "/")
fileBin := fSplit[len(fSplit)-1]
utils.Log(1, fmt.Sprintf("Attempting to load plugin '%v'", fileBin))
log.Infoln(fmt.Sprintf("Attempting to load plugin '%v'", fileBin))
ext := strings.Split(fileBin, ".")
if len(ext) != 2 {
utils.Log(3, fmt.Sprintf("Plugin '%v' must end in .so extension", fileBin))
log.Errorln(fmt.Sprintf("Plugin '%v' must end in .so extension", fileBin))
return fmt.Errorf("Plugin '%v' must end in .so extension %v", fileBin, len(ext))
}
if ext[1] != "so" {
utils.Log(3, fmt.Sprintf("Plugin '%v' must end in .so extension", fileBin))
log.Errorln(fmt.Sprintf("Plugin '%v' must end in .so extension", fileBin))
return fmt.Errorf("Plugin '%v' must end in .so extension", fileBin)
}
plug, err := plugin.Open(file)
if err != nil {
utils.Log(3, fmt.Sprintf("Plugin '%v' could not load correctly. %v", fileBin, err))
log.Errorln(fmt.Sprintf("Plugin '%v' could not load correctly. %v", fileBin, err))
return err
}
symPlugin, err := plug.Lookup("Plugin")
if err != nil {
utils.Log(3, fmt.Sprintf("Plugin '%v' could not locate Plugin variable. %v", fileBin, err))
log.Errorln(fmt.Sprintf("Plugin '%v' could not locate Plugin variable. %v", fileBin, err))
return err
}
plugActions, ok := symPlugin.(types.PluginActions)
if !ok {
utils.Log(3, fmt.Sprintf("Plugin %v was not type PluginObject", f.Name()))
log.Errorln(fmt.Sprintf("Plugin %v was not type PluginObject", f.Name()))
return fmt.Errorf("Plugin %v was not type PluginActions %v", f.Name(), plugActions.GetInfo())
}
info := plugActions.GetInfo()
@ -88,28 +89,28 @@ func LoadPlugin(file string) error {
if err != nil {
return err
}
utils.Log(1, fmt.Sprintf("Plugin %v loaded from %v", info.Name, f.Name()))
log.Infoln(fmt.Sprintf("Plugin %v loaded from %v", info.Name, f.Name()))
core.CoreApp.AllPlugins = append(core.CoreApp.AllPlugins, plugActions)
return nil
}
func LoadPlugins() {
pluginDir := dir + "/plugins"
utils.Log(1, fmt.Sprintf("Loading any available Plugins from /plugins directory"))
log.Infoln(fmt.Sprintf("Loading any available Plugins from /plugins directory"))
if _, err := os.Stat(pluginDir); os.IsNotExist(err) {
os.Mkdir(pluginDir, os.ModePerm)
}
files, err := ioutil.ReadDir(pluginDir)
if err != nil {
utils.Log(2, fmt.Sprintf("Plugins directory was not found. Error: %v", err))
log.Warnln(fmt.Sprintf("Plugins directory was not found. Error: %v", err))
return
}
for _, f := range files {
err := LoadPlugin(f.Name())
if err != nil {
utils.Log(3, err)
log.Errorln(err)
continue
}
}
utils.Log(1, fmt.Sprintf("Loaded %v Plugins", len(core.CoreApp.Plugins)))
log.Infoln(fmt.Sprintf("Loaded %v Plugins", len(core.CoreApp.Plugins)))
}

View File

@ -7,54 +7,66 @@
/* Mobile Service Container */
HTML, BODY {
background-color: #fcfcfc;
padding-bottom: 10px; }
padding-bottom: 10px;
}
.container {
padding-top: 20px;
padding-bottom: 25px;
max-width: 860px;
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important; }
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
}
.header-title {
color: #464646; }
color: #464646;
}
.header-desc {
color: #939393; }
color: #939393;
}
.btn {
border-radius: 0.2rem; }
border-radius: 0.2rem;
}
.online_list .badge {
margin-top: 0.2rem; }
margin-top: 0.2rem;
}
.navbar {
margin-bottom: 30px; }
margin-bottom: 30px;
}
.btn-sm {
line-height: 1.3;
font-size: 0.75rem; }
font-size: 0.75rem;
}
.view_service_btn {
position: absolute;
bottom: -40px;
right: 40px; }
right: 40px;
}
.service_lower_info {
position: absolute;
bottom: -40px;
left: 40px;
color: #d1ffca;
font-size: 0.85rem; }
font-size: 0.85rem;
}
.lg_number {
font-size: 2.3rem;
font-weight: bold;
display: block;
color: #4f4f4f; }
color: #4f4f4f;
}
.stats_area {
text-align: center;
color: #a5a5a5; }
color: #a5a5a5;
}
.lower_canvas {
height: 3.4rem;
@ -62,82 +74,101 @@ HTML, BODY {
background-color: #48d338;
padding: 15px 10px;
margin-left: 0px !important;
margin-right: 0px !important; }
margin-right: 0px !important;
}
.lower_canvas SPAN {
font-size: 1rem;
color: #fff; }
color: #fff;
}
.footer {
text-decoration: none;
margin-top: 20px; }
margin-top: 20px;
}
.footer A {
color: #8d8d8d;
text-decoration: none; }
text-decoration: none;
}
.footer A:HOVER {
color: #6d6d6d; }
color: #6d6d6d;
}
.badge {
color: white;
border-radius: 0.2rem; }
border-radius: 0.2rem;
}
.btn-group {
height: 25px; }
.btn-group A {
padding: 0.1rem .75rem;
font-size: 0.8rem; }
height: 25px;
}
.btn-group A {
padding: 0.1rem 0.75rem;
font-size: 0.8rem;
}
.card-body .badge {
color: #fff; }
color: #fff;
}
.nav-pills .nav-link {
border-radius: 0.2rem; }
border-radius: 0.2rem;
}
.form-control {
border-radius: 0.2rem; }
border-radius: 0.2rem;
}
.card {
background-color: #ffffff;
border: 1px solid rgba(0, 0, 0, 0.125); }
border: 1px solid rgba(0, 0, 0, 0.125);
}
.card-body {
overflow: hidden; }
overflow: hidden;
}
.card-body H4 A {
color: #444444;
text-decoration: none; }
text-decoration: none;
}
.chart-container {
position: relative;
height: 170px;
width: 100%;
overflow: hidden; }
overflow: hidden;
}
.service-chart-container {
position: relative;
height: 400px;
width: 100%; }
width: 100%;
}
.service-chart-heatmap {
position: relative;
height: 300px;
width: 100%; }
width: 100%;
}
.inputTags-field {
border: 0;
background-color: transparent;
padding-top: .13rem; }
padding-top: 0.13rem;
}
input.inputTags-field:focus {
outline-width: 0; }
outline-width: 0;
}
.inputTags-list {
display: block;
width: 100%;
min-height: calc(2.25rem + 2px);
padding: .2rem .35rem;
padding: 0.2rem 0.35rem;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
@ -145,8 +176,9 @@ input.inputTags-field:focus {
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: .25rem;
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out; }
border-radius: 0.25rem;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
.inputTags-item {
background-color: #3aba39;
@ -154,63 +186,81 @@ input.inputTags-field:focus {
padding: 5px 8px;
font-size: 10pt;
color: white;
border-radius: 4px; }
border-radius: 4px;
}
.inputTags-item .close-item {
margin-left: 6px;
font-size: 13pt;
font-weight: bold;
cursor: pointer; }
cursor: pointer;
}
.btn-primary {
background-color: #3e9bff;
border-color: #006fe6;
color: white; }
.btn-primary.dyn-dark {
background-color: #32a825 !important;
border-color: #2c9320 !important; }
.btn-primary.dyn-light {
background-color: #75de69 !important;
border-color: #88e37e !important; }
color: white;
}
.btn-primary.dyn-dark {
background-color: #32a825 !important;
border-color: #2c9320 !important;
}
.btn-primary.dyn-light {
background-color: #75de69 !important;
border-color: #88e37e !important;
}
.btn-success {
background-color: #47d337; }
.btn-success.dyn-dark {
background-color: #32a825 !important;
border-color: #2c9320 !important; }
.btn-success.dyn-light {
background-color: #75de69 !important;
border-color: #88e37e !important; }
background-color: #47d337;
}
.btn-success.dyn-dark {
background-color: #32a825 !important;
border-color: #2c9320 !important;
}
.btn-success.dyn-light {
background-color: #75de69 !important;
border-color: #88e37e !important;
}
.btn-danger {
background-color: #dd3545; }
.btn-danger.dyn-dark {
background-color: #b61f2d !important;
border-color: #a01b28 !important; }
.btn-danger.dyn-light {
background-color: #e66975 !important;
border-color: #e97f89 !important; }
background-color: #dd3545;
}
.btn-danger.dyn-dark {
background-color: #b61f2d !important;
border-color: #a01b28 !important;
}
.btn-danger.dyn-light {
background-color: #e66975 !important;
border-color: #e97f89 !important;
}
.bg-success {
background-color: #47d337 !important; }
background-color: #47d337 !important;
}
.bg-danger {
background-color: #dd3545 !important; }
background-color: #dd3545 !important;
}
.bg-success .dyn-dark {
background-color: #35b027 !important; }
background-color: #35b027 !important;
}
.bg-danger .dyn-dark {
background-color: #bf202f !important; }
background-color: #bf202f !important;
}
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
background-color: #13a00d; }
background-color: #13a00d;
}
.nav-pills A {
color: #424242; }
color: #424242;
}
.nav-pills I {
margin-right: 10px; }
margin-right: 10px;
}
.CodeMirror {
/* Bootstrap Settings */
@ -230,23 +280,26 @@ input.inputTags-field:focus {
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
/* Code Mirror Settings */
font-family: monospace;
position: relative;
overflow: hidden;
height: 80vh; }
height: 80vh;
}
.CodeMirror-focused {
/* Bootstrap Settings */
border-color: #66afe9;
outline: 0;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; }
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
}
.switch {
font-size: 1rem;
position: relative; }
position: relative;
}
.switch input {
position: absolute;
@ -257,7 +310,8 @@ input.inputTags-field:focus {
clip: rect(0 0 0 0);
clip-path: inset(50%);
overflow: hidden;
padding: 0; }
padding: 0;
}
.switch input + label {
position: relative;
@ -270,23 +324,26 @@ input.inputTags-field:focus {
outline: none;
user-select: none;
vertical-align: middle;
text-indent: calc(calc(calc(2.375rem * .8) * 2) + .5rem); }
text-indent: calc(calc(calc(2.375rem * .8) * 2) + .5rem);
}
.switch input + label::before,
.switch input + label::after {
content: '';
content: "";
position: absolute;
top: 0;
left: 0;
width: calc(calc(2.375rem * .8) * 2);
bottom: 0;
display: block; }
display: block;
}
.switch input + label::before {
right: 0;
background-color: #dee2e6;
border-radius: calc(2.375rem * .8);
transition: 0.2s all; }
transition: 0.2s all;
}
.switch input + label::after {
top: 2px;
@ -295,105 +352,137 @@ input.inputTags-field:focus {
height: calc(calc(2.375rem * .8) - calc(2px * 2));
border-radius: 50%;
background-color: white;
transition: 0.2s all; }
transition: 0.2s all;
}
.switch input:checked + label::before {
background-color: #08d; }
background-color: #08d;
}
.switch input:checked + label::after {
margin-left: calc(2.375rem * .8); }
margin-left: calc(2.375rem * .8);
}
.switch input:focus + label::before {
outline: none;
box-shadow: 0 0 0 0.2rem rgba(0, 136, 221, 0.25); }
box-shadow: 0 0 0 0.2rem rgba(0, 136, 221, 0.25);
}
.switch input:disabled + label {
color: #868e96;
cursor: not-allowed; }
cursor: not-allowed;
}
.switch input:disabled + label::before {
background-color: #e9ecef; }
background-color: #e9ecef;
}
.switch.switch-sm {
font-size: 0.875rem; }
font-size: 0.875rem;
}
.switch.switch-sm input + label {
min-width: calc(calc(1.9375rem * .8) * 2);
height: calc(1.9375rem * .8);
line-height: calc(1.9375rem * .8);
text-indent: calc(calc(calc(1.9375rem * .8) * 2) + .5rem); }
text-indent: calc(calc(calc(1.9375rem * .8) * 2) + .5rem);
}
.switch.switch-sm input + label::before {
width: calc(calc(1.9375rem * .8) * 2); }
width: calc(calc(1.9375rem * .8) * 2);
}
.switch.switch-sm input + label::after {
width: calc(calc(1.9375rem * .8) - calc(2px * 2));
height: calc(calc(1.9375rem * .8) - calc(2px * 2)); }
height: calc(calc(1.9375rem * .8) - calc(2px * 2));
}
.switch.switch-sm input:checked + label::after {
margin-left: calc(1.9375rem * .8); }
margin-left: calc(1.9375rem * .8);
}
.switch.switch-lg {
font-size: 1.25rem; }
font-size: 1.25rem;
}
.switch.switch-lg input + label {
min-width: calc(calc(3rem * .8) * 2);
height: calc(3rem * .8);
line-height: calc(3rem * .8);
text-indent: calc(calc(calc(3rem * .8) * 2) + .5rem); }
text-indent: calc(calc(calc(3rem * .8) * 2) + .5rem);
}
.switch.switch-lg input + label::before {
width: calc(calc(3rem * .8) * 2); }
width: calc(calc(3rem * .8) * 2);
}
.switch.switch-lg input + label::after {
width: calc(calc(3rem * .8) - calc(2px * 2));
height: calc(calc(3rem * .8) - calc(2px * 2)); }
height: calc(calc(3rem * .8) - calc(2px * 2));
}
.switch.switch-lg input:checked + label::after {
margin-left: calc(3rem * .8); }
margin-left: calc(3rem * .8);
}
.switch + .switch {
margin-left: 1rem; }
margin-left: 1rem;
}
@keyframes pulse_animation {
0% {
transform: scale(1); }
transform: scale(1);
}
30% {
transform: scale(1); }
transform: scale(1);
}
40% {
transform: scale(1.02); }
transform: scale(1.02);
}
50% {
transform: scale(1); }
transform: scale(1);
}
60% {
transform: scale(1); }
transform: scale(1);
}
70% {
transform: scale(1.05); }
transform: scale(1.05);
}
80% {
transform: scale(1); }
transform: scale(1);
}
100% {
transform: scale(1); } }
transform: scale(1);
}
}
.pulse {
animation-name: pulse_animation;
animation-duration: 1500ms;
transform-origin: 70% 70%;
animation-iteration-count: infinite;
animation-timing-function: linear; }
animation-timing-function: linear;
}
@keyframes glow-grow {
0% {
opacity: 0;
transform: scale(1); }
transform: scale(1);
}
80% {
opacity: 1; }
opacity: 1;
}
100% {
transform: scale(2);
opacity: 0; } }
opacity: 0;
}
}
.pulse-glow {
animation-name: glow-grown;
animation-duration: 100ms;
transform-origin: 70% 30%;
animation-iteration-count: infinite;
animation-timing-function: linear; }
animation-timing-function: linear;
}
.pulse-glow:before,
.pulse-glow:after {
@ -405,10 +494,12 @@ input.inputTags-field:focus {
right: 2.15rem;
border-radius: 0;
box-shadow: 0 0 6px #47d337;
animation: glow-grow 2s ease-out infinite; }
animation: glow-grow 2s ease-out infinite;
}
.sortable_drag {
background-color: #0000000f; }
background-color: #0000000f;
}
.drag_icon {
cursor: move;
@ -422,112 +513,139 @@ input.inputTags-field:focus {
margin-right: 5px;
margin-left: -10px;
text-align: center;
color: #b1b1b1; }
color: #b1b1b1;
}
/* (Optional) Apply a "closed-hand" cursor during drag operation. */
.drag_icon:active {
cursor: grabbing;
cursor: -moz-grabbing;
cursor: -webkit-grabbing; }
cursor: -webkit-grabbing;
}
.switch_btn {
float: right;
margin: -1px 0px 0px 0px;
display: block; }
display: block;
}
#start_container {
position: absolute;
z-index: 99999;
margin-top: 20px; }
margin-top: 20px;
}
#end_container {
position: absolute;
z-index: 99999;
margin-top: 20px;
right: 0; }
right: 0;
}
.pointer {
cursor: pointer; }
cursor: pointer;
}
.jumbotron {
background-color: white; }
background-color: white;
}
.toggle-service {
font-size: 18pt;
float: left;
margin: 2px 3px 0 0;
cursor: pointer; }
cursor: pointer;
}
@media (max-width: 767px) {
HTML, BODY {
background-color: #fcfcfc; }
background-color: #fcfcfc;
}
.sm-container {
margin-top: 0px !important;
padding: 0 !important; }
padding: 0 !important;
}
.list-group-item H5 {
font-size: 0.9rem; }
font-size: 0.9rem;
}
.container {
padding: 0px !important;
padding-top: 15px !important; }
padding-top: 15px !important;
}
.group_header {
margin-left: 15px; }
margin-left: 15px;
}
.navbar {
margin-left: 0px;
margin-top: 0px;
width: 100%;
margin-bottom: 0; }
margin-bottom: 0;
}
.btn-sm {
line-height: 0.9rem;
font-size: 0.65rem; }
font-size: 0.65rem;
}
.full-col-12 {
padding-left: 0px;
padding-right: 0px; }
padding-right: 0px;
}
.card {
border: 0;
border-radius: 0rem;
padding: 0;
background-color: #ffffff; }
background-color: #ffffff;
}
.card-body {
font-size: 10pt;
padding: 0px 10px; }
padding: 10px 10px;
}
.lg_number {
font-size: 7.8vw; }
font-size: 7.8vw;
}
.stats_area {
margin-top: 1.5rem !important;
margin-bottom: 1.5rem !important; }
margin-bottom: 1.5rem !important;
}
.stats_area .col-4 {
padding-left: 0;
padding-right: 0;
font-size: 0.6rem; }
font-size: 0.6rem;
}
.list-group-item {
border-top: 1px solid #e4e4e4;
border: 0px; }
border: 0px;
}
.list-group-item:first-child {
border-top-left-radius: 0;
border-top-right-radius: 0; }
border-top-right-radius: 0;
}
.list-group-item:last-child {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0; }
border-bottom-left-radius: 0;
}
.list-group-item P {
font-size: 0.7rem; }
font-size: 0.7rem;
}
.service-chart-container {
height: 200px; } }
height: 200px;
}
}
/*# sourceMappingURL=base.css.map */

View File

@ -67,7 +67,7 @@ func main() {
Compiled string
Pages map[string]string
}{
Timestamp: time.Now(),
Timestamp: utils.Now(),
URL: "ok",
Compiled: compiled,
Pages: newPages,

View File

@ -28,6 +28,7 @@ import (
)
var (
log = utils.Log.WithField("type", "source")
CssBox *rice.Box // CSS files from the 'source/css' directory, this will be loaded into '/assets/css'
ScssBox *rice.Box // SCSS files from the 'source/scss' directory, this will be loaded into '/assets/scss'
JsBox *rice.Box // JS files from the 'source/js' directory, this will be loaded into '/assets/js'
@ -60,24 +61,24 @@ func CompileSASS(folder string) error {
scssFile := fmt.Sprintf("%v/%v", folder, "assets/scss/base.scss")
baseFile := fmt.Sprintf("%v/%v", folder, "assets/css/base.css")
utils.Log(1, fmt.Sprintf("Compiling SASS %v into %v", scssFile, baseFile))
log.Infoln(fmt.Sprintf("Compiling SASS %v into %v", scssFile, baseFile))
command := fmt.Sprintf("%v %v %v", sassBin, scssFile, baseFile)
stdout, stderr, err := utils.Command(command)
if stdout != "" || stderr != "" {
utils.Log(3, fmt.Sprintf("Failed to compile assets with SASS %v", err))
log.Errorln(fmt.Sprintf("Failed to compile assets with SASS %v", err))
return errors.New("failed to capture stdout or stderr")
}
if err != nil {
utils.Log(3, fmt.Sprintf("Failed to compile assets with SASS %v", err))
utils.Log(3, fmt.Sprintf("sh -c %v", command))
log.Errorln(fmt.Sprintf("Failed to compile assets with SASS %v", err))
log.Errorln(fmt.Sprintf("sh -c %v", command))
return err
}
utils.Log(1, fmt.Sprintf("out: %v | error: %v", stdout, stderr))
utils.Log(1, "SASS Compiling is complete!")
log.Infoln(fmt.Sprintf("out: %v | error: %v", stdout, stderr))
log.Infoln("SASS Compiling is complete!")
return nil
}
@ -87,12 +88,12 @@ func UsingAssets(folder string) bool {
return true
} else {
if os.Getenv("USE_ASSETS") == "true" {
utils.Log(1, "Environment variable USE_ASSETS was found.")
log.Infoln("Environment variable USE_ASSETS was found.")
CreateAllAssets(folder)
err := CompileSASS(folder)
if err != nil {
CopyToPublic(CssBox, folder+"/css", "base.css")
utils.Log(2, "Default 'base.css' was insert because SASS did not work.")
log.Warnln("Default 'base.css' was insert because SASS did not work.")
return true
}
return true
@ -104,10 +105,10 @@ func UsingAssets(folder string) bool {
// SaveAsset will save an asset to the '/assets/' folder.
func SaveAsset(data []byte, folder, file string) error {
location := folder + "/assets/" + file
utils.Log(1, fmt.Sprintf("Saving %v", location))
log.Infoln(fmt.Sprintf("Saving %v", location))
err := utils.SaveFile(location, data)
if err != nil {
utils.Log(3, fmt.Sprintf("Failed to save %v, %v", location, err))
log.Errorln(fmt.Sprintf("Failed to save %v, %v", location, err))
return err
}
return nil
@ -117,7 +118,7 @@ func SaveAsset(data []byte, folder, file string) error {
func OpenAsset(folder, file string) string {
dat, err := ioutil.ReadFile(folder + "/assets/" + file)
if err != nil {
utils.Log(3, fmt.Sprintf("Failed to open %v, %v", file, err))
log.Errorln(fmt.Sprintf("Failed to open %v, %v", file, err))
return ""
}
return string(dat)
@ -125,14 +126,14 @@ func OpenAsset(folder, file string) string {
// CreateAllAssets will dump HTML, CSS, SCSS, and JS assets into the '/assets' directory
func CreateAllAssets(folder string) error {
utils.Log(1, fmt.Sprintf("Dump Statping assets into %v/assets", folder))
log.Infoln(fmt.Sprintf("Dump Statping assets into %v/assets", folder))
MakePublicFolder(folder + "/assets")
MakePublicFolder(folder + "/assets/js")
MakePublicFolder(folder + "/assets/css")
MakePublicFolder(folder + "/assets/scss")
MakePublicFolder(folder + "/assets/font")
MakePublicFolder(folder + "/assets/files")
utils.Log(1, "Inserting scss, css, and javascript files into assets folder")
log.Infoln("Inserting scss, css, and javascript files into assets folder")
CopyAllToPublic(ScssBox, "scss")
CopyAllToPublic(FontBox, "font")
CopyAllToPublic(CssBox, "css")
@ -144,9 +145,9 @@ func CreateAllAssets(folder string) error {
CopyToPublic(TmplBox, folder+"/assets/files", "swagger.json")
CopyToPublic(TmplBox, folder+"/assets/files", "postman.json")
CopyToPublic(TmplBox, folder+"/assets/files", "grafana.json")
utils.Log(1, "Compiling CSS from SCSS style...")
log.Infoln("Compiling CSS from SCSS style...")
err := CompileSASS(utils.Directory)
utils.Log(1, "Statping assets have been inserted")
log.Infoln("Statping assets have been inserted")
return err
}
@ -154,10 +155,10 @@ func CreateAllAssets(folder string) error {
func DeleteAllAssets(folder string) error {
err := os.RemoveAll(folder + "/assets")
if err != nil {
utils.Log(1, fmt.Sprintf("There was an issue deleting Statping Assets, %v", err))
log.Infoln(fmt.Sprintf("There was an issue deleting Statping Assets, %v", err))
return err
}
utils.Log(1, "Statping assets have been deleted")
log.Infoln("Statping assets have been deleted")
return err
}
@ -184,15 +185,15 @@ func CopyAllToPublic(box *rice.Box, folder string) error {
// CopyToPublic will create a file from a rice Box to the '/assets' directory
func CopyToPublic(box *rice.Box, folder, file string) error {
assetFolder := fmt.Sprintf("%v/%v", folder, file)
utils.Log(1, fmt.Sprintf("Copying %v to %v", file, assetFolder))
log.Infoln(fmt.Sprintf("Copying %v to %v", file, assetFolder))
base, err := box.String(file)
if err != nil {
utils.Log(3, fmt.Sprintf("Failed to copy %v to %v, %v.", file, assetFolder, err))
log.Errorln(fmt.Sprintf("Failed to copy %v to %v, %v.", file, assetFolder, err))
return err
}
err = ioutil.WriteFile(assetFolder, []byte(base), 0744)
if err != nil {
utils.Log(3, fmt.Sprintf("Failed to write file %v to %v, %v.", file, assetFolder, err))
log.Errorln(fmt.Sprintf("Failed to write file %v to %v, %v.", file, assetFolder, err))
return err
}
return nil
@ -200,11 +201,11 @@ func CopyToPublic(box *rice.Box, folder, file string) error {
// MakePublicFolder will create a new folder
func MakePublicFolder(folder string) error {
utils.Log(1, fmt.Sprintf("Creating folder '%v'", folder))
log.Infoln(fmt.Sprintf("Creating folder '%v'", folder))
if _, err := os.Stat(folder); os.IsNotExist(err) {
err = os.MkdirAll(folder, 0777)
if err != nil {
utils.Log(3, fmt.Sprintf("Failed to created %v directory, %v", folder, err))
log.Errorln(fmt.Sprintf("Failed to created %v directory, %v", folder, err))
return err
}
}

View File

@ -46,7 +46,7 @@
<div class="jumbotron jumbotron-fluid">
<div class="text-center">
<h1 class="display-4">No Services!</h1>
<a class="lead">You don't have any websites or applications being monitored by your Statping server. <p><a href="/services" class="btn btn-secondary mt-3">Add Service</a></p></p>
<a class="lead">You don't have any websites or applications being monitored by your Statping server. <p><a href="/service/create" class="btn btn-secondary mt-3">Add Service</a></p></p>
</div>
</div>
{{end}}

View File

@ -47,7 +47,7 @@
{{ if not .Services }}
<div class="alert alert-danger" role="alert">
<h4 class="alert-heading">No Services to Monitor!</h4>
<p>Your Statping Status Page is working correctly, but you don't have any services to monitor. Go to the <b>Dashboard</b> and add a website to begin really using your status page!</p>
<p>Your Statping Status Page is working correctly, but you don't have any services to monitor. Go to the <a href="/dashboard">Dashboard</a> and add a website to begin really using your status page!</p>
<hr>
<p class="mb-0">If this is a bug, please make an issue in the Statping Github Repo. <a href="https://github.com/hunterlong/statping" class="btn btn-sm btn-outline-danger float-right">Statping Github Repo</a></p>
</div>

View File

@ -41,6 +41,7 @@
</div>
<div class="col-12 mt-5">
{{if ne (len (Groups false)) 0}}
<h1 class="text-muted">Groups</h1>
<table class="table">
<thead>
@ -67,6 +68,7 @@
{{end}}
</tbody>
</table>
{{end}}
{{if Auth}}
<h1 class="text-muted mt-5">Create Group</h1>
{{template "form_group" NewGroup}}

File diff suppressed because one or more lines are too long

View File

@ -17,24 +17,137 @@ package utils
import (
"fmt"
"github.com/fatih/structs"
"github.com/hunterlong/statping/types"
Logger "github.com/sirupsen/logrus"
"gopkg.in/natefinch/lumberjack.v2"
"log"
"net/http"
"io"
"os"
"os/signal"
"reflect"
"runtime"
"strings"
"sync"
"syscall"
"time"
)
var (
logFile *os.File
fmtLogs *log.Logger
ljLogger *lumberjack.Logger
LastLines []*LogRow
LockLines sync.Mutex
Log = Logger.StandardLogger()
ljLogger *lumberjack.Logger
LastLines []*LogRow
LockLines sync.Mutex
VerboseMode int
callerInitOnce sync.Once
logrusPackage string
minimumCallerDepth = 1
)
const logFilePath = "/logs/statping.log"
type hook struct {
Entries []Logger.Entry
mu sync.RWMutex
}
func (t *hook) Fire(e *Logger.Entry) error {
pushLastLine(e.Message)
return nil
}
func (t *hook) Levels() []Logger.Level {
return Logger.AllLevels
}
// LogCaller retrieves the name of the first non-logrus calling function
func LogCaller(min int) *runtime.Frame {
const maximumCallerDepth = 25
var minimumCallerDepth = min
// Restrict the lookback frames to avoid runaway lookups
pcs := make([]uintptr, maximumCallerDepth)
depth := runtime.Callers(minimumCallerDepth, pcs)
frames := runtime.CallersFrames(pcs[:depth])
// cache this package's fully-qualified name
callerInitOnce.Do(func() {
logrusPackage = getPackageName(runtime.FuncForPC(pcs[0]).Name())
fmt.Println("caller once", logrusPackage, minimumCallerDepth, frames)
})
for f, again := frames.Next(); again; f, again = frames.Next() {
fmt.Println(f.Func, f.File, f.Line)
return &f
}
// if we got here, we failed to find the caller's context
return nil
}
func getPackageName(f string) string {
for {
lastPeriod := strings.LastIndex(f, ".")
lastSlash := strings.LastIndex(f, "/")
if lastPeriod > lastSlash {
f = f[:lastPeriod]
} else {
break
}
}
return f
}
// ToFields accepts any amount of interfaces to create a new mapping for log.Fields. You will need to
// turn on verbose mode by starting Statping with "-v". This function will convert a struct of to the
// base struct name, and each field into it's own mapping, for example:
// type "*types.Service", on string field "Name" converts to "service_name=value". There is also an
// additional field called "_pointer" that will return the pointer hex value.
func ToFields(d ...interface{}) map[string]interface{} {
if VerboseMode == 1 {
return nil
}
fieldKey := make(map[string]interface{})
for _, v := range d {
spl := strings.Split(fmt.Sprintf("%T", v), ".")
trueType := spl[len(spl)-1]
if !structs.IsStruct(v) {
continue
}
for _, f := range structs.Fields(v) {
if f.IsExported() && !f.IsZero() && f.Kind() != reflect.Ptr && f.Kind() != reflect.Slice && f.Kind() != reflect.Chan {
field := strings.ToLower(trueType + "_" + f.Name())
fieldKey[field] = replaceVal(f.Value())
}
}
fieldKey[strings.ToLower(trueType+"_pointer")] = fmt.Sprintf("%p", v)
}
return fieldKey
}
// replaceVal accepts an interface to be converted into human readable type
func replaceVal(d interface{}) interface{} {
switch v := d.(type) {
case types.NullBool:
return v.Bool
case types.NullString:
return v.String
case types.NullFloat64:
return v.Float64
case types.NullInt64:
return v.Int64
case string:
if len(v) > 500 {
return v[:500] + "... (truncated in logs)"
}
return v
case time.Time:
return v.String()
case time.Duration:
return v.String()
default:
return d
}
}
// createLog will create the '/logs' directory based on a directory
func createLog(dir string) error {
var err error
@ -46,11 +159,6 @@ func createLog(dir string) error {
return err
}
}
file, err := os.Create(dir + "/logs/statup.log")
if err != nil {
return err
}
defer file.Close()
return err
}
@ -60,77 +168,42 @@ func InitLogs() error {
if err != nil {
return err
}
logFile, err = os.OpenFile(Directory+"/logs/statup.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0755)
if err != nil {
log.Printf("ERROR opening file: %v", err)
return err
}
ljLogger = &lumberjack.Logger{
Filename: Directory + "/logs/statup.log",
Filename: Directory + logFilePath,
MaxSize: 16,
MaxBackups: 3,
MaxBackups: 5,
MaxAge: 28,
}
fmtLogs = log.New(logFile, "", log.Ldate|log.Ltime)
log.SetOutput(ljLogger)
rotate()
mw := io.MultiWriter(os.Stdout, ljLogger)
Log.SetOutput(mw)
Log.SetFormatter(&Logger.TextFormatter{
ForceColors: true,
DisableColors: false,
})
Log.AddHook(new(hook))
Log.SetNoLock()
if VerboseMode == 1 {
Log.SetLevel(Logger.WarnLevel)
} else if VerboseMode == 2 {
Log.SetLevel(Logger.InfoLevel)
} else if VerboseMode == 3 {
Log.SetLevel(Logger.DebugLevel)
} else {
Log.SetReportCaller(true)
Log.SetLevel(Logger.TraceLevel)
}
LastLines = make([]*LogRow, 0)
return err
}
func rotate() {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP)
go func() {
for {
<-c
ljLogger.Rotate()
}
}()
}
// Log creates a new entry in the Logger. Log has 1-5 levels depending on how critical the log/error is
func Log(level int, err interface{}) error {
if disableLogs {
return nil
}
pushLastLine(err)
var outErr error
switch level {
case 5:
_, outErr = fmt.Printf("PANIC: %v\n", err)
fmtLogs.Printf("PANIC: %v\n", err)
case 4:
_, outErr = fmt.Printf("FATAL: %v\n", err)
fmtLogs.Printf("FATAL: %v\n", err)
//color.Red("ERROR: %v\n", err)
//os.Exit(2)
case 3:
_, outErr = fmt.Printf("ERROR: %v\n", err)
fmtLogs.Printf("ERROR: %v\n", err)
//color.Red("ERROR: %v\n", err)
case 2:
_, outErr = fmt.Printf("WARNING: %v\n", err)
fmtLogs.Printf("WARNING: %v\n", err)
//color.Yellow("WARNING: %v\n", err)
case 1:
_, outErr = fmt.Printf("INFO: %v\n", err)
fmtLogs.Printf("INFO: %v\n", err)
//color.Blue("INFO: %v\n", err)
case 0:
_, outErr = fmt.Printf("%v\n", err)
fmtLogs.Printf("%v\n", err)
//color.White("%v\n", err)
}
return outErr
}
// Http returns a log for a HTTP request
func Http(r *http.Request) string {
msg := fmt.Sprintf("%v (%v) | IP: %v", r.RequestURI, r.Method, r.Host)
fmt.Printf("WEB: %v\n", msg)
pushLastLine(msg)
return msg
// CloseLogs will close the log file correctly on shutdown
func CloseLogs() {
ljLogger.Rotate()
Log.Writer().Close()
ljLogger.Close()
}
func pushLastLine(line interface{}) {

View File

@ -34,6 +34,11 @@ func Timezoner(t time.Time, zone float32) time.Time {
return timez
}
// Now returns the UTC timestamp
func Now() time.Time {
return time.Now().UTC()
}
// FormatDuration converts a time.Duration into a string
func FormatDuration(d time.Duration) string {
var out string

View File

@ -176,7 +176,7 @@ func FileExists(name string) bool {
// DeleteFile will attempt to delete a file
// DeleteFile("newfile.json")
func DeleteFile(file string) error {
Log(1, "deleting file: "+file)
Log.Infoln("deleting file: " + file)
err := os.Remove(file)
if err != nil {
return err
@ -193,7 +193,7 @@ func DeleteDirectory(directory string) error {
// Command will run a terminal command with 'sh -c COMMAND' and return stdout and errOut as strings
// in, out, err := Command("sass assets/scss assets/css/base.css")
func Command(cmd string) (string, string, error) {
Log(1, "running command: "+cmd)
Log.Infoln("running command: " + cmd)
testCmd := exec.Command("sh", "-c", cmd)
var stdout, stderr []byte
var errStdout, errStderr error

View File

@ -16,7 +16,6 @@
package utils
import (
"errors"
"fmt"
"github.com/stretchr/testify/assert"
"net/http"
@ -42,7 +41,8 @@ func TestCreateLog(t *testing.T) {
func TestInitLogs(t *testing.T) {
assert.Nil(t, InitLogs())
assert.FileExists(t, Directory+"/logs/statup.log")
Log.Infoln("this is a test")
assert.FileExists(t, Directory+"/logs/statping.log")
}
func TestDir(t *testing.T) {
@ -57,15 +57,6 @@ func TestCommand(t *testing.T) {
assert.Empty(t, out)
}
func TestLog(t *testing.T) {
assert.Nil(t, Log(0, errors.New("this is a 0 level error")))
assert.Nil(t, Log(1, errors.New("this is a 1 level error")))
assert.Nil(t, Log(2, errors.New("this is a 2 level error")))
assert.Nil(t, Log(3, errors.New("this is a 3 level error")))
assert.Nil(t, Log(4, errors.New("this is a 4 level error")))
assert.Nil(t, Log(5, errors.New("this is a 5 level error")))
}
func TestToInt(t *testing.T) {
assert.Equal(t, int64(55), ToInt("55"))
assert.Equal(t, int64(55), ToInt(55))
@ -126,7 +117,7 @@ func ExampleDurationReadable() {
func TestLogHTTP(t *testing.T) {
req, err := http.NewRequest("GET", "/", nil)
assert.Nil(t, err)
assert.NotEmpty(t, Http(req))
assert.NotNil(t, req)
}
func TestStringInt(t *testing.T) {

View File

@ -1 +1 @@
0.80.66
0.80.67