Add log-format option

This commit is contained in:
Julien Riou 2019-02-16 11:47:30 +01:00
parent fac5099c9b
commit 24eb4fe203
No known key found for this signature in database
GPG key ID: BA3E15176E45E85D
9 changed files with 61 additions and 44 deletions

View file

@ -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.

View file

@ -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"`

View file

@ -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

View file

@ -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")

View file

@ -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

View file

@ -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))
}
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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))
}
}