Add logging control
This commit is contained in:
parent
96ade1c1d7
commit
f0d3cd5bdf
9 changed files with 164 additions and 24 deletions
|
@ -2,9 +2,9 @@ package base
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/jouir/pgterminate/log"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -64,7 +64,7 @@ func (c *Config) Read(file string) error {
|
||||||
|
|
||||||
// Reload reads from file and update configuration
|
// Reload reads from file and update configuration
|
||||||
func (c *Config) Reload() {
|
func (c *Config) Reload() {
|
||||||
log.Println("Reloading configuration")
|
log.Debug("Reloading configuration")
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
defer c.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
if c.File != "" {
|
if c.File != "" {
|
||||||
|
|
14
base/db.go
14
base/db.go
|
@ -2,8 +2,9 @@ package base
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"github.com/jouir/pgterminate/log"
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -42,7 +43,15 @@ func (db *Db) Disconnect() {
|
||||||
|
|
||||||
// Sessions connects to the database and returns current sessions
|
// Sessions connects to the database and returns current sessions
|
||||||
func (db *Db) Sessions() (sessions []Session) {
|
func (db *Db) Sessions() (sessions []Session) {
|
||||||
query := `select pid as pid, usename as user, datname as db, host(client_addr)::text || ':' || client_port::text as client, state as state, substring(query from 1 for ` + strconv.Itoa(maxQueryLength) + `) as query, coalesce(extract(epoch from now() - state_change), 0) as "stateDuration" from pg_catalog.pg_stat_activity where pid <> pg_backend_pid();`
|
query := fmt.Sprintf(`select pid as pid,
|
||||||
|
usename as user,
|
||||||
|
datname as db,
|
||||||
|
host(client_addr)::text || ':' || client_port::text as client,
|
||||||
|
state as state, substring(query from 1 for %d) as query,
|
||||||
|
coalesce(extract(epoch from now() - state_change), 0) as "stateDuration"
|
||||||
|
from pg_catalog.pg_stat_activity
|
||||||
|
where pid <> pg_backend_pid();`, maxQueryLength)
|
||||||
|
log.Debugf("query: %s\n", query)
|
||||||
rows, err := db.conn.Query(query)
|
rows, err := db.conn.Query(query)
|
||||||
Panic(err)
|
Panic(err)
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
@ -70,6 +79,7 @@ func (db *Db) TerminateSessions(sessions []Session) {
|
||||||
}
|
}
|
||||||
if len(pids) > 0 {
|
if len(pids) > 0 {
|
||||||
query := `select pg_terminate_backend(pid) from pg_stat_activity where pid = any($1);`
|
query := `select pg_terminate_backend(pid) from pg_stat_activity where pid = any($1);`
|
||||||
|
log.Debugf("query: %s\n", query)
|
||||||
_, err := db.conn.Exec(query, pq.Array(pids))
|
_, err := db.conn.Exec(query, pq.Array(pids))
|
||||||
Panic(err)
|
Panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
package base
|
package base
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"github.com/jouir/pgterminate/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Panic prints a non-nil error and terminates the program
|
// Panic prints a non-nil error and terminates the program
|
||||||
func Panic(err error) {
|
func Panic(err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalf("%s\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,10 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/jouir/pgterminate/base"
|
"github.com/jouir/pgterminate/base"
|
||||||
|
"github.com/jouir/pgterminate/log"
|
||||||
"github.com/jouir/pgterminate/notifier"
|
"github.com/jouir/pgterminate/notifier"
|
||||||
"github.com/jouir/pgterminate/terminator"
|
"github.com/jouir/pgterminate/terminator"
|
||||||
"golang.org/x/crypto/ssh/terminal"
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -23,6 +23,9 @@ func main() {
|
||||||
var err error
|
var err error
|
||||||
config := base.NewConfig()
|
config := base.NewConfig()
|
||||||
|
|
||||||
|
quiet := flag.Bool("quiet", false, "Quiet mode")
|
||||||
|
verbose := flag.Bool("verbose", false, "Verbose mode")
|
||||||
|
debug := flag.Bool("debug", false, "Debug mode")
|
||||||
version := flag.Bool("version", false, "Print version")
|
version := flag.Bool("version", false, "Print version")
|
||||||
flag.StringVar(&config.File, "config", "", "Configuration file")
|
flag.StringVar(&config.File, "config", "", "Configuration file")
|
||||||
flag.StringVar(&config.Host, "host", "", "Instance host address")
|
flag.StringVar(&config.Host, "host", "", "Instance host address")
|
||||||
|
@ -42,6 +45,17 @@ func main() {
|
||||||
flag.StringVar(&config.SyslogFacility, "syslog-facility", "", "Define syslog facility from LOCAL0 to LOCAL7")
|
flag.StringVar(&config.SyslogFacility, "syslog-facility", "", "Define syslog facility from LOCAL0 to LOCAL7")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
log.SetLevel(log.WarnLevel)
|
||||||
|
if *debug {
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
}
|
||||||
|
if *verbose {
|
||||||
|
log.SetLevel(log.InfoLevel)
|
||||||
|
}
|
||||||
|
if *quiet {
|
||||||
|
log.SetLevel(log.ErrorLevel)
|
||||||
|
}
|
||||||
|
|
||||||
if *version {
|
if *version {
|
||||||
if AppVersion == "" {
|
if AppVersion == "" {
|
||||||
AppVersion = "unknown"
|
AppVersion = "unknown"
|
||||||
|
@ -64,18 +78,18 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.ActiveTimeout == 0 && config.IdleTimeout == 0 {
|
if config.ActiveTimeout == 0 && config.IdleTimeout == 0 {
|
||||||
log.Fatalln("Parameter -active-timeout or -idle-timeout required")
|
log.Fatal("Parameter -active-timeout or -idle-timeout required")
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.LogDestination != "console" && config.LogDestination != "file" && config.LogDestination != "syslog" {
|
if config.LogDestination != "console" && config.LogDestination != "file" && config.LogDestination != "syslog" {
|
||||||
log.Fatalln("Log destination must be 'console', 'file' or 'syslog'")
|
log.Fatal("Log destination must be 'console', 'file' or 'syslog'")
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.LogDestination == "syslog" && config.SyslogFacility != "" {
|
if config.LogDestination == "syslog" && config.SyslogFacility != "" {
|
||||||
matched, err := regexp.MatchString("^LOCAL[0-7]$", config.SyslogFacility)
|
matched, err := regexp.MatchString("^LOCAL[0-7]$", config.SyslogFacility)
|
||||||
base.Panic(err)
|
base.Panic(err)
|
||||||
if !matched {
|
if !matched {
|
||||||
log.Fatalln("Syslog facility must range from LOCAL0 to LOCAL7")
|
log.Fatal("Syslog facility must range from LOCAL0 to LOCAL7")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,7 +129,7 @@ func handleSignals(ctx *base.Context, n notifier.Notifier) {
|
||||||
signal.Notify(c, syscall.SIGTERM)
|
signal.Notify(c, syscall.SIGTERM)
|
||||||
go func() {
|
go func() {
|
||||||
for sig := range c {
|
for sig := range c {
|
||||||
log.Printf("Received %v signal\n", sig)
|
log.Debugf("Received %v signal\n", sig)
|
||||||
close(ctx.Sessions)
|
close(ctx.Sessions)
|
||||||
ctx.Done <- true
|
ctx.Done <- true
|
||||||
}
|
}
|
||||||
|
@ -126,7 +140,7 @@ func handleSignals(ctx *base.Context, n notifier.Notifier) {
|
||||||
signal.Notify(h, syscall.SIGHUP)
|
signal.Notify(h, syscall.SIGHUP)
|
||||||
go func() {
|
go func() {
|
||||||
for sig := range h {
|
for sig := range h {
|
||||||
log.Printf("Received %v signal\n", sig)
|
log.Debugf("Received %v signal\n", sig)
|
||||||
ctx.Config.Reload()
|
ctx.Config.Reload()
|
||||||
n.Reload()
|
n.Reload()
|
||||||
}
|
}
|
||||||
|
@ -135,7 +149,7 @@ func handleSignals(ctx *base.Context, n notifier.Notifier) {
|
||||||
|
|
||||||
// writePid writes current pid into a pid file
|
// writePid writes current pid into a pid file
|
||||||
func writePid(file string) {
|
func writePid(file string) {
|
||||||
log.Println("Creating pid file", file)
|
log.Infof("Creating pid file %s", file)
|
||||||
pid := strconv.Itoa(os.Getpid())
|
pid := strconv.Itoa(os.Getpid())
|
||||||
|
|
||||||
f, err := os.OpenFile(file, os.O_CREATE|os.O_WRONLY, 0644)
|
f, err := os.OpenFile(file, os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
|
@ -149,7 +163,7 @@ func writePid(file string) {
|
||||||
// removePid removes pid file
|
// removePid removes pid file
|
||||||
func removePid(file string) {
|
func removePid(file string) {
|
||||||
if _, err := os.Stat(file); err == nil {
|
if _, err := os.Stat(file); err == nil {
|
||||||
log.Println("Removing pid file", file)
|
log.Infof("Removing pid file %s", file)
|
||||||
err := os.Remove(file)
|
err := os.Remove(file)
|
||||||
base.Panic(err)
|
base.Panic(err)
|
||||||
}
|
}
|
||||||
|
|
107
log/log.go
Normal file
107
log/log.go
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DebugLevel for debug messages
|
||||||
|
DebugLevel int = iota
|
||||||
|
// InfoLevel for info messages
|
||||||
|
InfoLevel
|
||||||
|
// WarnLevel for warning messages
|
||||||
|
WarnLevel
|
||||||
|
// ErrorLevel for error messages
|
||||||
|
ErrorLevel
|
||||||
|
// FatalLevel for fatal messages
|
||||||
|
FatalLevel
|
||||||
|
)
|
||||||
|
|
||||||
|
var level int
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
level = WarnLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLevel configures logging level
|
||||||
|
func SetLevel(logLevel int) error {
|
||||||
|
if logLevel < DebugLevel || logLevel > FatalLevel {
|
||||||
|
return errors.New("Wrong logging level")
|
||||||
|
}
|
||||||
|
level = logLevel
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug prints debug messages
|
||||||
|
func Debug(m string) {
|
||||||
|
if level <= DebugLevel {
|
||||||
|
log.Println("[DEBUG] " + m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugf prints debug messages with printf format
|
||||||
|
func Debugf(format string, values ...interface{}) {
|
||||||
|
if level <= DebugLevel {
|
||||||
|
log.Printf("[DEBUG] "+format, values...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info prints info messages
|
||||||
|
func Info(m string) {
|
||||||
|
if level <= InfoLevel {
|
||||||
|
log.Println("[INFO] " + m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof prints info messages with printf format
|
||||||
|
func Infof(format string, values ...interface{}) {
|
||||||
|
if level <= InfoLevel {
|
||||||
|
log.Printf("[INFO] "+format, values...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn prints warning messages
|
||||||
|
func Warn(m string) {
|
||||||
|
if level <= WarnLevel {
|
||||||
|
log.Println("[WARN] " + m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnf prints warning messages with printf format
|
||||||
|
func Warnf(format string, values ...interface{}) {
|
||||||
|
if level <= WarnLevel {
|
||||||
|
log.Printf("[WARN] "+format, values...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error prints error messages
|
||||||
|
func Error(m string) {
|
||||||
|
if level <= ErrorLevel {
|
||||||
|
log.Println("[ERROR] " + m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf prints error messages with printf format
|
||||||
|
func Errorf(format string, values ...interface{}) {
|
||||||
|
if level <= WarnLevel {
|
||||||
|
log.Printf("[ERROR] "+format, values...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal prints fatal messages and exit
|
||||||
|
func Fatal(m string) {
|
||||||
|
if level <= FatalLevel {
|
||||||
|
log.Println("[FATAL] " + m)
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalf prints fatal messages with printf format and exit
|
||||||
|
func Fatalf(format string, values ...interface{}) {
|
||||||
|
if level <= FatalLevel {
|
||||||
|
log.Printf("[FATAL] "+format, values...)
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jouir/pgterminate/base"
|
"github.com/jouir/pgterminate/base"
|
||||||
"log"
|
"github.com/jouir/pgterminate/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Console notifier structure
|
// Console notifier structure
|
||||||
|
@ -19,11 +19,13 @@ func NewConsole(sessions chan base.Session) Notifier {
|
||||||
|
|
||||||
// Run starts console notifier
|
// Run starts console notifier
|
||||||
func (c *Console) Run() {
|
func (c *Console) Run() {
|
||||||
|
log.Info("Starting console notifier")
|
||||||
for session := range c.sessions {
|
for session := range c.sessions {
|
||||||
log.Printf("%s", session)
|
log.Infof("%s\n", session)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload for handling SIGHUP signals
|
// Reload for handling SIGHUP signals
|
||||||
func (c *Console) Reload() {
|
func (c *Console) Reload() {
|
||||||
|
log.Info("Reloading console notifier")
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package notifier
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jouir/pgterminate/base"
|
"github.com/jouir/pgterminate/base"
|
||||||
"log"
|
"github.com/jouir/pgterminate/log"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -26,6 +26,7 @@ func NewFile(name string, sessions chan base.Session) Notifier {
|
||||||
|
|
||||||
// Run starts the file notifier
|
// Run starts the file notifier
|
||||||
func (f *File) Run() {
|
func (f *File) Run() {
|
||||||
|
log.Info("Starting file notifier")
|
||||||
f.open()
|
f.open()
|
||||||
defer f.terminate()
|
defer f.terminate()
|
||||||
|
|
||||||
|
@ -45,15 +46,16 @@ func (f *File) open() {
|
||||||
|
|
||||||
// Reload closes and re-open the file to be compatible with logrotate
|
// Reload closes and re-open the file to be compatible with logrotate
|
||||||
func (f *File) Reload() {
|
func (f *File) Reload() {
|
||||||
|
log.Info("Reloading file notifier")
|
||||||
f.mutex.Lock()
|
f.mutex.Lock()
|
||||||
defer f.mutex.Unlock()
|
defer f.mutex.Unlock()
|
||||||
log.Println("Re-opening log file", f.name)
|
log.Debugf("Re-opening log file %s\n", f.name)
|
||||||
f.handle.Close()
|
f.handle.Close()
|
||||||
f.open()
|
f.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
// terminate closes the file
|
// terminate closes the file
|
||||||
func (f *File) terminate() {
|
func (f *File) terminate() {
|
||||||
log.Println("Closing log file", f.name)
|
log.Debugf("Closing log file %s\n", f.name)
|
||||||
f.handle.Close()
|
f.handle.Close()
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package notifier
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/jouir/pgterminate/base"
|
"github.com/jouir/pgterminate/base"
|
||||||
|
"github.com/jouir/pgterminate/log"
|
||||||
"log/syslog"
|
"log/syslog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -44,6 +45,7 @@ func NewSyslog(facility string, ident string, sessions chan base.Session) Notifi
|
||||||
|
|
||||||
// Run starts syslog notifier
|
// Run starts syslog notifier
|
||||||
func (s *Syslog) Run() {
|
func (s *Syslog) Run() {
|
||||||
|
log.Info("Starting syslog notifier")
|
||||||
var err error
|
var err error
|
||||||
if s.writer, err = syslog.New(s.priority, s.ident); err != nil {
|
if s.writer, err = syslog.New(s.priority, s.ident); err != nil {
|
||||||
base.Panic(err)
|
base.Panic(err)
|
||||||
|
@ -56,7 +58,9 @@ func (s *Syslog) Run() {
|
||||||
// Reload disconnect from syslog daemon and re-connect
|
// Reload disconnect from syslog daemon and re-connect
|
||||||
// Executed when receiving SIGHUP signal
|
// Executed when receiving SIGHUP signal
|
||||||
func (s *Syslog) Reload() {
|
func (s *Syslog) Reload() {
|
||||||
|
log.Info("Reloading syslog notifier")
|
||||||
if s.writer != nil {
|
if s.writer != nil {
|
||||||
|
log.Debug("Re-connecting to syslog daemon")
|
||||||
s.disconnect()
|
s.disconnect()
|
||||||
s.connect()
|
s.connect()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package terminator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jouir/pgterminate/base"
|
"github.com/jouir/pgterminate/base"
|
||||||
"log"
|
"github.com/jouir/pgterminate/log"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,8 +26,9 @@ func NewTerminator(ctx *base.Context) *Terminator {
|
||||||
|
|
||||||
// Run starts the Terminator
|
// Run starts the Terminator
|
||||||
func (t *Terminator) Run() {
|
func (t *Terminator) Run() {
|
||||||
|
log.Info("Starting terminator")
|
||||||
t.db = base.NewDb(t.config.Dsn())
|
t.db = base.NewDb(t.config.Dsn())
|
||||||
log.Println("Connecting to instance")
|
log.Info("Connecting to instance")
|
||||||
t.db.Connect()
|
t.db.Connect()
|
||||||
defer t.terminate()
|
defer t.terminate()
|
||||||
|
|
||||||
|
@ -39,12 +40,12 @@ func (t *Terminator) Run() {
|
||||||
sessions := t.db.Sessions()
|
sessions := t.db.Sessions()
|
||||||
if t.config.ActiveTimeout != 0 {
|
if t.config.ActiveTimeout != 0 {
|
||||||
actives := activeSessions(sessions, t.config.ActiveTimeout)
|
actives := activeSessions(sessions, t.config.ActiveTimeout)
|
||||||
go t.terminateAndNotify(actives)
|
t.terminateAndNotify(actives)
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.config.IdleTimeout != 0 {
|
if t.config.IdleTimeout != 0 {
|
||||||
idles := idleSessions(sessions, t.config.IdleTimeout)
|
idles := idleSessions(sessions, t.config.IdleTimeout)
|
||||||
go t.terminateAndNotify(idles)
|
t.terminateAndNotify(idles)
|
||||||
}
|
}
|
||||||
time.Sleep(time.Duration(t.config.Interval*1000) * time.Millisecond)
|
time.Sleep(time.Duration(t.config.Interval*1000) * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
@ -62,7 +63,7 @@ func (t *Terminator) terminateAndNotify(sessions []base.Session) {
|
||||||
|
|
||||||
// terminate terminates gracefully
|
// terminate terminates gracefully
|
||||||
func (t *Terminator) terminate() {
|
func (t *Terminator) terminate() {
|
||||||
log.Println("Disconnecting from instance")
|
log.Info("Disconnecting from instance")
|
||||||
t.db.Disconnect()
|
t.db.Disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue