Add log-format option
This commit is contained in:
parent
fac5099c9b
commit
24eb4fe203
9 changed files with 61 additions and 44 deletions
11
README.md
11
README.md
|
@ -94,5 +94,16 @@ When exclude users list or regex is set and no include option is set, `pgtermina
|
|||
|
||||
LISTEN queries are asynchronous. Sessions are set to "idle" state even if they are waiting for messages to be sent to the queue. `pgterminate` can exclude sessions in that state by looking at the last known query starting with "LISTEN", with the `exclude-listeners` parameter.
|
||||
|
||||
# Log format
|
||||
|
||||
The following placeholders are available to format log messages using `log-format` option:
|
||||
* `%p`: pid
|
||||
* `%u`: username
|
||||
* `%d`: database name
|
||||
* `%r`: client (host:port)
|
||||
* `%s`: state
|
||||
* `%m`: state duration
|
||||
* `%q`: query
|
||||
|
||||
# License
|
||||
`pgterminate` is released under [The Unlicense](LICENSE) license. Code is under public domain.
|
||||
|
|
|
@ -2,13 +2,14 @@ package base
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/jouir/pgterminate/log"
|
||||
"gopkg.in/yaml.v2"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/jouir/pgterminate/log"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// AppName exposes application name to config module
|
||||
|
@ -29,6 +30,7 @@ type Config struct {
|
|||
ActiveTimeout float64 `yaml:"active-timeout"`
|
||||
LogDestination string `yaml:"log-destination"`
|
||||
LogFile string `yaml:"log-file"`
|
||||
LogFormat string `yaml:"log-format"`
|
||||
PidFile string `yaml:"pid-file"`
|
||||
SyslogIdent string `yaml:"syslog-ident"`
|
||||
SyslogFacility string `yaml:"syslog-facility"`
|
||||
|
|
|
@ -29,31 +29,25 @@ func NewSession(pid int64, user string, db string, client string, state string,
|
|||
}
|
||||
}
|
||||
|
||||
// String represents a Session as a string
|
||||
func (s *Session) String() string {
|
||||
var output []string
|
||||
if s.Pid != 0 {
|
||||
output = append(output, fmt.Sprintf("pid=%d", s.Pid))
|
||||
// Format returns a Session as a string by replacing placeholders with their respective value
|
||||
func (s *Session) Format(format string) string {
|
||||
definitions := map[string]string{
|
||||
"%p": fmt.Sprintf("%d", s.Pid),
|
||||
"%u": s.User,
|
||||
"%d": s.Db,
|
||||
"%r": s.Client,
|
||||
"%s": s.State,
|
||||
"%m": fmt.Sprintf("%f", s.StateDuration),
|
||||
"%q": s.Query,
|
||||
}
|
||||
if s.User != "" {
|
||||
output = append(output, fmt.Sprintf("user=%s", s.User))
|
||||
|
||||
output := format
|
||||
|
||||
for placeholder, value := range definitions {
|
||||
output = strings.Replace(output, placeholder, value, -1)
|
||||
}
|
||||
if s.Db != "" {
|
||||
output = append(output, fmt.Sprintf("db=%s", s.Db))
|
||||
}
|
||||
if s.Client != "" {
|
||||
output = append(output, fmt.Sprintf("client=%s", s.Client))
|
||||
}
|
||||
if s.State != "" {
|
||||
output = append(output, fmt.Sprintf("state=%s", s.State))
|
||||
}
|
||||
if s.StateDuration != 0 {
|
||||
output = append(output, fmt.Sprintf("state_duration=%f", s.StateDuration))
|
||||
}
|
||||
if s.Query != "" && !s.IsIdle() {
|
||||
output = append(output, fmt.Sprintf("query=%s", s.Query))
|
||||
}
|
||||
return strings.Join(output, " ")
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
// IsIdle returns true when a session is doing nothing
|
||||
|
|
|
@ -3,17 +3,18 @@ package main
|
|||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/jouir/pgterminate/base"
|
||||
"github.com/jouir/pgterminate/log"
|
||||
"github.com/jouir/pgterminate/notifier"
|
||||
"github.com/jouir/pgterminate/terminator"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"os"
|
||||
"os/signal"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/jouir/pgterminate/base"
|
||||
"github.com/jouir/pgterminate/log"
|
||||
"github.com/jouir/pgterminate/notifier"
|
||||
"github.com/jouir/pgterminate/terminator"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
// AppVersion stores application version at compilation time
|
||||
|
@ -40,6 +41,7 @@ func main() {
|
|||
flag.Float64Var(&config.ActiveTimeout, "active-timeout", 0, "Time for active connections to be terminated in seconds")
|
||||
flag.StringVar(&config.LogDestination, "log-destination", "console", "Log destination between 'console', 'syslog' or 'file'")
|
||||
flag.StringVar(&config.LogFile, "log-file", "", "Write logs to a file")
|
||||
flag.StringVar(&config.LogFormat, "log-format", "pid=%p user=%u db=%d client=%r state=%s state_duration=%m query=%q", "Represent messages using this format")
|
||||
flag.StringVar(&config.PidFile, "pid-file", "", "Write process id into a file")
|
||||
flag.StringVar(&config.SyslogIdent, "syslog-ident", "pgterminate", "Define syslog tag")
|
||||
flag.StringVar(&config.SyslogFacility, "syslog-facility", "", "Define syslog facility from LOCAL0 to LOCAL7")
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#idle-timeout: 300
|
||||
#active-timeout: 10
|
||||
#log-file: /var/log/pgterminate/pgterminate.log
|
||||
#log-format: 'pid=%p user=%u db=%d client=%r state=%s state_duration=%m query=%q'
|
||||
#pid-file: /var/run/pgterminate/pgterminate.pid
|
||||
#log-destination: console|file|syslog
|
||||
#syslog-ident: pgterminate
|
||||
|
@ -20,4 +21,4 @@
|
|||
# user1
|
||||
# user2
|
||||
#exclude-users-regex: "(user1|user2)"
|
||||
#cancel
|
||||
#cancel
|
|
@ -7,12 +7,14 @@ import (
|
|||
|
||||
// Console notifier structure
|
||||
type Console struct {
|
||||
format string
|
||||
sessions chan *base.Session
|
||||
}
|
||||
|
||||
// NewConsole creates a console notifier
|
||||
func NewConsole(sessions chan *base.Session) Notifier {
|
||||
func NewConsole(format string, sessions chan *base.Session) Notifier {
|
||||
return &Console{
|
||||
format: format,
|
||||
sessions: sessions,
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +23,7 @@ func NewConsole(sessions chan *base.Session) Notifier {
|
|||
func (c *Console) Run() {
|
||||
log.Info("Starting console notifier")
|
||||
for session := range c.sessions {
|
||||
log.Infof("%s\n", session)
|
||||
log.Info(session.Format(c.format))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,25 +1,28 @@
|
|||
package notifier
|
||||
|
||||
import (
|
||||
"github.com/jouir/pgterminate/base"
|
||||
"github.com/jouir/pgterminate/log"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jouir/pgterminate/base"
|
||||
"github.com/jouir/pgterminate/log"
|
||||
)
|
||||
|
||||
// File structure for file notifier
|
||||
type File struct {
|
||||
handle *os.File
|
||||
format string
|
||||
name string
|
||||
sessions chan *base.Session
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// NewFile creates a file notifier
|
||||
func NewFile(name string, sessions chan *base.Session) Notifier {
|
||||
func NewFile(format string, name string, sessions chan *base.Session) Notifier {
|
||||
return &File{
|
||||
name: name,
|
||||
format: format,
|
||||
sessions: sessions,
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +35,7 @@ func (f *File) Run() {
|
|||
|
||||
for session := range f.sessions {
|
||||
timestamp := time.Now().Format(time.RFC3339)
|
||||
_, err := f.handle.WriteString(timestamp + " " + session.String() + "\n")
|
||||
_, err := f.handle.WriteString(timestamp + " " + session.Format(f.format) + "\n")
|
||||
base.Panic(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,10 +15,10 @@ type Notifier interface {
|
|||
func NewNotifier(ctx *base.Context) Notifier {
|
||||
switch ctx.Config.LogDestination {
|
||||
case "file":
|
||||
return NewFile(ctx.Config.LogFile, ctx.Sessions)
|
||||
return NewFile(ctx.Config.LogFile, ctx.Config.LogFormat, ctx.Sessions)
|
||||
case "syslog":
|
||||
return NewSyslog(ctx.Config.SyslogFacility, ctx.Config.SyslogIdent, ctx.Sessions)
|
||||
return NewSyslog(ctx.Config.SyslogFacility, ctx.Config.SyslogIdent, ctx.Config.LogFormat, ctx.Sessions)
|
||||
default: // console
|
||||
return NewConsole(ctx.Sessions)
|
||||
return NewConsole(ctx.Config.LogFormat, ctx.Sessions)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
package notifier
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/syslog"
|
||||
|
||||
"github.com/jouir/pgterminate/base"
|
||||
"github.com/jouir/pgterminate/log"
|
||||
"log/syslog"
|
||||
)
|
||||
|
||||
// Syslog notifier
|
||||
type Syslog struct {
|
||||
sessions chan *base.Session
|
||||
ident string
|
||||
format string
|
||||
priority syslog.Priority
|
||||
writer *syslog.Writer
|
||||
}
|
||||
|
||||
// NewSyslog creates a syslog notifier
|
||||
func NewSyslog(facility string, ident string, sessions chan *base.Session) Notifier {
|
||||
func NewSyslog(facility string, ident string, format string, sessions chan *base.Session) Notifier {
|
||||
var priority syslog.Priority
|
||||
switch facility {
|
||||
case "LOCAL0":
|
||||
|
@ -40,6 +41,7 @@ func NewSyslog(facility string, ident string, sessions chan *base.Session) Notif
|
|||
sessions: sessions,
|
||||
ident: ident,
|
||||
priority: priority,
|
||||
format: format,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,7 +53,7 @@ func (s *Syslog) Run() {
|
|||
base.Panic(err)
|
||||
}
|
||||
for session := range s.sessions {
|
||||
s.writer.Info(fmt.Sprintf("%s", session))
|
||||
s.writer.Info(session.Format(s.format))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue