statping/utils/log.go

221 lines
5.4 KiB
Go
Raw Normal View History

2018-12-04 05:57:11 +00:00
// Statping
2018-08-16 06:22:20 +00:00
// Copyright (C) 2018. Hunter Long and the project contributors
// Written by Hunter Long <info@socialeck.com> and the project contributors
//
2018-12-04 04:17:29 +00:00
// https://github.com/hunterlong/statping
2018-08-16 06:22:20 +00:00
//
// The licenses for most software and other practical works are designed
// to take away your freedom to share and change the works. By contrast,
// the GNU General Public License is intended to guarantee your freedom to
// share and change all versions of a program--to make sure it remains free
// software for all its users.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
2018-06-30 00:57:05 +00:00
package utils
import (
"fmt"
"github.com/fatih/structs"
2020-03-09 16:54:55 +00:00
"github.com/getsentry/sentry-go"
2020-03-04 10:29:00 +00:00
"github.com/hunterlong/statping/types/null"
Logger "github.com/sirupsen/logrus"
2018-06-30 00:57:05 +00:00
"gopkg.in/natefinch/lumberjack.v2"
"io"
2018-06-30 00:57:05 +00:00
"os"
"reflect"
"strings"
"sync"
"time"
2018-06-30 00:57:05 +00:00
)
var (
2019-12-30 03:34:49 +00:00
Log = Logger.StandardLogger()
ljLogger *lumberjack.Logger
LastLines []*logRow
LockLines sync.Mutex
VerboseMode int
2018-06-30 00:57:05 +00:00
)
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
}
// 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:
2020-03-04 10:29:00 +00:00
// type "*services.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{} {
2019-12-30 04:51:28 +00:00
if !Log.IsLevelEnabled(Logger.DebugLevel) {
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) {
2020-03-04 10:29:00 +00:00
case null.NullBool:
return v.Bool
2020-03-04 10:29:00 +00:00
case null.NullString:
return v.String
2020-03-04 10:29:00 +00:00
case null.NullFloat64:
return v.Float64
2020-03-04 10:29:00 +00:00
case null.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
2018-08-16 02:22:10 +00:00
func createLog(dir string) error {
2019-12-30 03:34:49 +00:00
if !FolderExists(dir + "/logs") {
2019-12-30 04:51:28 +00:00
CreateDirectory(dir + "/logs")
}
2019-12-30 03:34:49 +00:00
return nil
2018-08-16 02:22:10 +00:00
}
2018-07-27 07:06:26 +00:00
// InitLogs will create the '/logs' directory and creates a file '/logs/statup.log' for application logging
2018-08-16 02:22:10 +00:00
func InitLogs() error {
2019-12-30 08:08:51 +00:00
if err := createLog(Directory); err != nil {
2018-08-16 02:22:10 +00:00
return err
}
2018-06-30 00:57:05 +00:00
ljLogger = &lumberjack.Logger{
Filename: Directory + logFilePath,
2018-06-30 00:57:05 +00:00
MaxSize: 16,
MaxBackups: 5,
2018-06-30 00:57:05 +00:00
MaxAge: 28,
}
2020-03-08 18:13:27 +00:00
mw := io.MultiWriter(os.Stdout, ljLogger)
Log.SetOutput(mw)
2018-06-30 00:57:05 +00:00
Log.SetFormatter(&Logger.TextFormatter{
ForceColors: true,
DisableColors: false,
})
2019-12-29 21:01:53 +00:00
checkVerboseMode()
2018-06-30 00:57:05 +00:00
2020-03-09 16:54:55 +00:00
sentry.CaptureMessage("It works!")
2019-12-30 03:34:49 +00:00
LastLines = make([]*logRow, 0)
2019-12-30 08:08:51 +00:00
return nil
2019-12-29 21:01:53 +00:00
}
2019-12-30 08:08:51 +00:00
// checkVerboseMode will reset the Logging verbose setting. You can set
// the verbose level with "-v 3" or by setting VERBOSE=3 environment variable.
2019-12-29 21:01:53 +00:00
// statping -v 1 (only Warnings)
2019-12-30 08:08:51 +00:00
// statping -v 2 (Info and Warnings, default)
2019-12-29 21:01:53 +00:00
// statping -v 3 (Info, Warnings and Debug)
// statping -v 4 (Info, Warnings, Debug and Traces (SQL queries))
func checkVerboseMode() {
switch VerboseMode {
case 1:
Log.SetLevel(Logger.WarnLevel)
2019-12-29 21:01:53 +00:00
case 2:
Log.SetLevel(Logger.InfoLevel)
2019-12-29 21:01:53 +00:00
case 3:
Log.SetLevel(Logger.DebugLevel)
2019-12-29 21:01:53 +00:00
case 4:
Log.SetReportCaller(true)
Log.SetLevel(Logger.TraceLevel)
2019-12-29 21:01:53 +00:00
default:
Log.SetLevel(Logger.InfoLevel)
2018-06-30 00:57:05 +00:00
}
2019-12-30 08:08:51 +00:00
Log.Debugf("logging running in %v mode", Log.GetLevel().String())
2018-06-30 00:57:05 +00:00
}
// CloseLogs will close the log file correctly on shutdown
func CloseLogs() {
ljLogger.Rotate()
Log.Writer().Close()
ljLogger.Close()
2018-06-30 00:57:05 +00:00
}
func pushLastLine(line interface{}) {
LockLines.Lock()
defer LockLines.Unlock()
LastLines = append(LastLines, newLogRow(line))
// We want to store max 1000 lines in memory (for /logs page).
for len(LastLines) > 1000 {
LastLines = LastLines[1:]
}
}
// GetLastLine returns 1 line for a recent log entry
2019-12-30 03:34:49 +00:00
func GetLastLine() *logRow {
LockLines.Lock()
defer LockLines.Unlock()
if len(LastLines) > 0 {
2018-08-21 01:22:42 +00:00
return LastLines[len(LastLines)-1]
}
return nil
}
2019-12-30 03:34:49 +00:00
type logRow struct {
Date time.Time
Line interface{}
}
2019-12-30 03:34:49 +00:00
func newLogRow(line interface{}) (lgRow *logRow) {
lgRow = new(logRow)
lgRow.Date = time.Now()
lgRow.Line = line
return
}
2019-12-30 03:34:49 +00:00
func (o *logRow) lineAsString() string {
switch v := o.Line.(type) {
case string:
return v
case error:
return v.Error()
case []byte:
return string(v)
}
return ""
}
2019-12-30 03:34:49 +00:00
func (o *logRow) FormatForHtml() string {
return fmt.Sprintf("%s: %s", o.Date.Format("2006-01-02 15:04:05"), o.lineAsString())
}