WIP: Logging

This commit is contained in:
Philipp Heckel 2022-05-29 22:14:14 -04:00
parent 1f38a4a531
commit dc0e699fb5
8 changed files with 184 additions and 71 deletions

View file

@ -19,7 +19,7 @@ const (
) )
var flagsAccess = append( var flagsAccess = append(
userCommandFlags(), flagsUser,
&cli.BoolFlag{Name: "reset", Aliases: []string{"r"}, Usage: "reset access for user (and topic)"}, &cli.BoolFlag{Name: "reset", Aliases: []string{"r"}, Usage: "reset access for user (and topic)"},
) )
@ -28,7 +28,7 @@ var cmdAccess = &cli.Command{
Usage: "Grant/revoke access to a topic, or show access", Usage: "Grant/revoke access to a topic, or show access",
UsageText: "ntfy access [USERNAME [TOPIC [PERMISSION]]]", UsageText: "ntfy access [USERNAME [TOPIC [PERMISSION]]]",
Flags: flagsAccess, Flags: flagsAccess,
Before: initConfigFileInputSourceFunc("config", flagsAccess), Before: initLogFunc(initConfigFileInputSourceFunc("config", flagsAccess)),
Action: execUserAccess, Action: execUserAccess,
Category: categoryServer, Category: categoryServer,
Description: `Manage the access control list for the ntfy server. Description: `Manage the access control list for the ntfy server.

View file

@ -3,6 +3,7 @@ package cmd
import ( import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"heckel.io/ntfy/log"
"os" "os"
) )
@ -13,6 +14,11 @@ const (
var commands = make([]*cli.Command, 0) var commands = make([]*cli.Command, 0)
var flagsDefault = []cli.Flag{
&cli.BoolFlag{Name: "debug", Aliases: []string{"d"}, EnvVars: []string{"NTFY_DEBUG"}, Usage: "enable debug logging"},
&cli.StringFlag{Name: "log-level", Aliases: []string{"log_level"}, Value: log.InfoLevel.String(), EnvVars: []string{"NTFY_LOG_LEVEL"}, Usage: "set log level"},
}
// New creates a new CLI application // New creates a new CLI application
func New() *cli.App { func New() *cli.App {
return &cli.App{ return &cli.App{
@ -25,5 +31,23 @@ func New() *cli.App {
Writer: os.Stdout, Writer: os.Stdout,
ErrWriter: os.Stderr, ErrWriter: os.Stderr,
Commands: commands, Commands: commands,
Flags: flagsDefault,
Before: initLogFunc(nil),
}
}
func initLogFunc(next cli.BeforeFunc) cli.BeforeFunc {
return func(c *cli.Context) error {
if c.Bool("debug") {
log.SetLevel(log.DebugLevel)
} else {
log.SetLevel(log.ToLevel(c.String("log-level")))
}
if next != nil {
if err := next(c); err != nil {
return err
}
}
return nil
} }
} }

View file

@ -16,31 +16,35 @@ func init() {
commands = append(commands, cmdPublish) commands = append(commands, cmdPublish)
} }
var flagsPublish = append(
flagsDefault,
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG"}, Usage: "client config file"},
&cli.StringFlag{Name: "title", Aliases: []string{"t"}, EnvVars: []string{"NTFY_TITLE"}, Usage: "message title"},
&cli.StringFlag{Name: "priority", Aliases: []string{"p"}, EnvVars: []string{"NTFY_PRIORITY"}, Usage: "priority of the message (1=min, 2=low, 3=default, 4=high, 5=max)"},
&cli.StringFlag{Name: "tags", Aliases: []string{"tag", "T"}, EnvVars: []string{"NTFY_TAGS"}, Usage: "comma separated list of tags and emojis"},
&cli.StringFlag{Name: "delay", Aliases: []string{"at", "in", "D"}, EnvVars: []string{"NTFY_DELAY"}, Usage: "delay/schedule message"},
&cli.StringFlag{Name: "click", Aliases: []string{"U"}, EnvVars: []string{"NTFY_CLICK"}, Usage: "URL to open when notification is clicked"},
&cli.StringFlag{Name: "actions", Aliases: []string{"A"}, EnvVars: []string{"NTFY_ACTIONS"}, Usage: "actions JSON array or simple definition"},
&cli.StringFlag{Name: "attach", Aliases: []string{"a"}, EnvVars: []string{"NTFY_ATTACH"}, Usage: "URL to send as an external attachment"},
&cli.StringFlag{Name: "filename", Aliases: []string{"name", "n"}, EnvVars: []string{"NTFY_FILENAME"}, Usage: "filename for the attachment"},
&cli.StringFlag{Name: "file", Aliases: []string{"f"}, EnvVars: []string{"NTFY_FILE"}, Usage: "file to upload as an attachment"},
&cli.StringFlag{Name: "email", Aliases: []string{"mail", "e"}, EnvVars: []string{"NTFY_EMAIL"}, Usage: "also send to e-mail address"},
&cli.StringFlag{Name: "user", Aliases: []string{"u"}, EnvVars: []string{"NTFY_USER"}, Usage: "username[:password] used to auth against the server"},
&cli.BoolFlag{Name: "no-cache", Aliases: []string{"C"}, EnvVars: []string{"NTFY_NO_CACHE"}, Usage: "do not cache message server-side"},
&cli.BoolFlag{Name: "no-firebase", Aliases: []string{"F"}, EnvVars: []string{"NTFY_NO_FIREBASE"}, Usage: "do not forward message to Firebase"},
&cli.BoolFlag{Name: "env-topic", Aliases: []string{"P"}, EnvVars: []string{"NTFY_ENV_TOPIC"}, Usage: "use topic from NTFY_TOPIC env variable"},
&cli.BoolFlag{Name: "quiet", Aliases: []string{"q"}, EnvVars: []string{"NTFY_QUIET"}, Usage: "do not print message"},
)
var cmdPublish = &cli.Command{ var cmdPublish = &cli.Command{
Name: "publish", Name: "publish",
Aliases: []string{"pub", "send", "trigger"}, Aliases: []string{"pub", "send", "trigger"},
Usage: "Send message via a ntfy server", Usage: "Send message via a ntfy server",
UsageText: "ntfy send [OPTIONS..] TOPIC [MESSAGE]\n NTFY_TOPIC=.. ntfy send [OPTIONS..] -P [MESSAGE]", UsageText: "ntfy publish [OPTIONS..] TOPIC [MESSAGE]\nNTFY_TOPIC=.. ntfy publish [OPTIONS..] -P [MESSAGE]",
Action: execPublish, Action: execPublish,
Category: categoryClient, Category: categoryClient,
Flags: []cli.Flag{ Flags: flagsPublish,
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG"}, Usage: "client config file"}, Before: initLogFunc(nil),
&cli.StringFlag{Name: "title", Aliases: []string{"t"}, EnvVars: []string{"NTFY_TITLE"}, Usage: "message title"},
&cli.StringFlag{Name: "priority", Aliases: []string{"p"}, EnvVars: []string{"NTFY_PRIORITY"}, Usage: "priority of the message (1=min, 2=low, 3=default, 4=high, 5=max)"},
&cli.StringFlag{Name: "tags", Aliases: []string{"tag", "T"}, EnvVars: []string{"NTFY_TAGS"}, Usage: "comma separated list of tags and emojis"},
&cli.StringFlag{Name: "delay", Aliases: []string{"at", "in", "D"}, EnvVars: []string{"NTFY_DELAY"}, Usage: "delay/schedule message"},
&cli.StringFlag{Name: "click", Aliases: []string{"U"}, EnvVars: []string{"NTFY_CLICK"}, Usage: "URL to open when notification is clicked"},
&cli.StringFlag{Name: "actions", Aliases: []string{"A"}, EnvVars: []string{"NTFY_ACTIONS"}, Usage: "actions JSON array or simple definition"},
&cli.StringFlag{Name: "attach", Aliases: []string{"a"}, EnvVars: []string{"NTFY_ATTACH"}, Usage: "URL to send as an external attachment"},
&cli.StringFlag{Name: "filename", Aliases: []string{"name", "n"}, EnvVars: []string{"NTFY_FILENAME"}, Usage: "filename for the attachment"},
&cli.StringFlag{Name: "file", Aliases: []string{"f"}, EnvVars: []string{"NTFY_FILE"}, Usage: "file to upload as an attachment"},
&cli.StringFlag{Name: "email", Aliases: []string{"mail", "e"}, EnvVars: []string{"NTFY_EMAIL"}, Usage: "also send to e-mail address"},
&cli.StringFlag{Name: "user", Aliases: []string{"u"}, EnvVars: []string{"NTFY_USER"}, Usage: "username[:password] used to auth against the server"},
&cli.BoolFlag{Name: "no-cache", Aliases: []string{"C"}, EnvVars: []string{"NTFY_NO_CACHE"}, Usage: "do not cache message server-side"},
&cli.BoolFlag{Name: "no-firebase", Aliases: []string{"F"}, EnvVars: []string{"NTFY_NO_FIREBASE"}, Usage: "do not forward message to Firebase"},
&cli.BoolFlag{Name: "env-topic", Aliases: []string{"P"}, EnvVars: []string{"NTFY_ENV_TOPIC"}, Usage: "use topic from NTFY_TOPIC env variable"},
&cli.BoolFlag{Name: "quiet", Aliases: []string{"q"}, EnvVars: []string{"NTFY_QUIET"}, Usage: "do print message"},
},
Description: `Publish a message to a ntfy server. Description: `Publish a message to a ntfy server.
Examples: Examples:

View file

@ -5,7 +5,7 @@ package cmd
import ( import (
"errors" "errors"
"fmt" "fmt"
"log" "heckel.io/ntfy/log"
"math" "math"
"net" "net"
"strings" "strings"
@ -21,7 +21,8 @@ func init() {
commands = append(commands, cmdServe) commands = append(commands, cmdServe)
} }
var flagsServe = []cli.Flag{ var flagsServe = append(
flagsDefault,
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG_FILE"}, Value: "/etc/ntfy/server.yml", DefaultText: "/etc/ntfy/server.yml", Usage: "config file"}, &cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG_FILE"}, Value: "/etc/ntfy/server.yml", DefaultText: "/etc/ntfy/server.yml", Usage: "config file"},
altsrc.NewStringFlag(&cli.StringFlag{Name: "base-url", Aliases: []string{"base_url", "B"}, EnvVars: []string{"NTFY_BASE_URL"}, Usage: "externally visible base URL for this host (e.g. https://ntfy.sh)"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "base-url", Aliases: []string{"base_url", "B"}, EnvVars: []string{"NTFY_BASE_URL"}, Usage: "externally visible base URL for this host (e.g. https://ntfy.sh)"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-http", Aliases: []string{"listen_http", "l"}, EnvVars: []string{"NTFY_LISTEN_HTTP"}, Value: server.DefaultListenHTTP, Usage: "ip:port used to as HTTP listen address"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "listen-http", Aliases: []string{"listen_http", "l"}, EnvVars: []string{"NTFY_LISTEN_HTTP"}, Value: server.DefaultListenHTTP, Usage: "ip:port used to as HTTP listen address"}),
@ -59,7 +60,7 @@ var flagsServe = []cli.Flag{
altsrc.NewIntFlag(&cli.IntFlag{Name: "visitor-email-limit-burst", Aliases: []string{"visitor_email_limit_burst"}, EnvVars: []string{"NTFY_VISITOR_EMAIL_LIMIT_BURST"}, Value: server.DefaultVisitorEmailLimitBurst, Usage: "initial limit of e-mails per visitor"}), altsrc.NewIntFlag(&cli.IntFlag{Name: "visitor-email-limit-burst", Aliases: []string{"visitor_email_limit_burst"}, EnvVars: []string{"NTFY_VISITOR_EMAIL_LIMIT_BURST"}, Value: server.DefaultVisitorEmailLimitBurst, Usage: "initial limit of e-mails per visitor"}),
altsrc.NewDurationFlag(&cli.DurationFlag{Name: "visitor-email-limit-replenish", Aliases: []string{"visitor_email_limit_replenish"}, EnvVars: []string{"NTFY_VISITOR_EMAIL_LIMIT_REPLENISH"}, Value: server.DefaultVisitorEmailLimitReplenish, Usage: "interval at which burst limit is replenished (one per x)"}), altsrc.NewDurationFlag(&cli.DurationFlag{Name: "visitor-email-limit-replenish", Aliases: []string{"visitor_email_limit_replenish"}, EnvVars: []string{"NTFY_VISITOR_EMAIL_LIMIT_REPLENISH"}, Value: server.DefaultVisitorEmailLimitReplenish, Usage: "interval at which burst limit is replenished (one per x)"}),
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "behind-proxy", Aliases: []string{"behind_proxy", "P"}, EnvVars: []string{"NTFY_BEHIND_PROXY"}, Value: false, Usage: "if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting)"}), altsrc.NewBoolFlag(&cli.BoolFlag{Name: "behind-proxy", Aliases: []string{"behind_proxy", "P"}, EnvVars: []string{"NTFY_BEHIND_PROXY"}, Value: false, Usage: "if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting)"}),
} )
var cmdServe = &cli.Command{ var cmdServe = &cli.Command{
Name: "serve", Name: "serve",
@ -68,7 +69,7 @@ var cmdServe = &cli.Command{
Action: execServe, Action: execServe,
Category: categoryServer, Category: categoryServer,
Flags: flagsServe, Flags: flagsServe,
Before: initConfigFileInputSourceFunc("config", flagsServe), Before: initLogFunc(initConfigFileInputSourceFunc("config", flagsServe)),
Description: `Run the ntfy server and listen for incoming requests Description: `Run the ntfy server and listen for incoming requests
The command will load the configuration from /etc/ntfy/server.yml. Config options can The command will load the configuration from /etc/ntfy/server.yml. Config options can
@ -192,7 +193,7 @@ func execServe(c *cli.Context) error {
for _, host := range visitorRequestLimitExemptHosts { for _, host := range visitorRequestLimitExemptHosts {
ips, err := net.LookupIP(host) ips, err := net.LookupIP(host)
if err != nil { if err != nil {
log.Printf("cannot resolve host %s: %s, ignoring visitor request exemption", host, err.Error()) log.Warn("cannot resolve host %s: %s, ignoring visitor request exemption", host, err.Error())
continue continue
} }
for _, ip := range ips { for _, ip := range ips {
@ -242,12 +243,12 @@ func execServe(c *cli.Context) error {
conf.EnableWeb = enableWeb conf.EnableWeb = enableWeb
s, err := server.New(conf) s, err := server.New(conf)
if err != nil { if err != nil {
log.Fatalln(err) log.Fatal(err)
} }
if err := s.Run(); err != nil { if err := s.Run(); err != nil {
log.Fatalln(err) log.Fatal(err)
} }
log.Printf("Exiting.") log.Info("Exiting.")
return nil return nil
} }

View file

@ -24,6 +24,17 @@ const (
clientUserConfigFileWindowsRelative = "ntfy\\client.yml" clientUserConfigFileWindowsRelative = "ntfy\\client.yml"
) )
var flagsSubscribe = append(
flagsDefault,
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, Usage: "client config file"},
&cli.StringFlag{Name: "since", Aliases: []string{"s"}, Usage: "return events since `SINCE` (Unix timestamp, or all)"},
&cli.StringFlag{Name: "user", Aliases: []string{"u"}, Usage: "username[:password] used to auth against the server"},
&cli.BoolFlag{Name: "from-config", Aliases: []string{"C"}, Usage: "read subscriptions from config file (service mode)"},
&cli.BoolFlag{Name: "poll", Aliases: []string{"p"}, Usage: "return events and exit, do not listen for new events"},
&cli.BoolFlag{Name: "scheduled", Aliases: []string{"sched", "S"}, Usage: "also return scheduled/delayed events"},
&cli.BoolFlag{Name: "verbose", Aliases: []string{"v"}, Usage: "print verbose output"},
)
var cmdSubscribe = &cli.Command{ var cmdSubscribe = &cli.Command{
Name: "subscribe", Name: "subscribe",
Aliases: []string{"sub"}, Aliases: []string{"sub"},
@ -31,15 +42,8 @@ var cmdSubscribe = &cli.Command{
UsageText: "ntfy subscribe [OPTIONS..] [TOPIC]", UsageText: "ntfy subscribe [OPTIONS..] [TOPIC]",
Action: execSubscribe, Action: execSubscribe,
Category: categoryClient, Category: categoryClient,
Flags: []cli.Flag{ Flags: flagsSubscribe,
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, Usage: "client config file"}, Before: initLogFunc(nil),
&cli.StringFlag{Name: "since", Aliases: []string{"s"}, Usage: "return events since `SINCE` (Unix timestamp, or all)"},
&cli.StringFlag{Name: "user", Aliases: []string{"u"}, Usage: "username[:password] used to auth against the server"},
&cli.BoolFlag{Name: "from-config", Aliases: []string{"C"}, Usage: "read subscriptions from config file (service mode)"},
&cli.BoolFlag{Name: "poll", Aliases: []string{"p"}, Usage: "return events and exit, do not listen for new events"},
&cli.BoolFlag{Name: "scheduled", Aliases: []string{"sched", "S"}, Usage: "also return scheduled/delayed events"},
&cli.BoolFlag{Name: "verbose", Aliases: []string{"v"}, Usage: "print verbose output"},
},
Description: `Subscribe to a topic from a ntfy server, and either print or execute a command for Description: `Subscribe to a topic from a ntfy server, and either print or execute a command for
every arriving message. There are 3 modes in which the command can be run: every arriving message. There are 3 modes in which the command can be run:

View file

@ -17,14 +17,19 @@ func init() {
commands = append(commands, cmdUser) commands = append(commands, cmdUser)
} }
var flagsUser = userCommandFlags() var flagsUser = append(
flagsDefault,
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG_FILE"}, Value: "/etc/ntfy/server.yml", DefaultText: "/etc/ntfy/server.yml", Usage: "config file"},
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-file", Aliases: []string{"H"}, EnvVars: []string{"NTFY_AUTH_FILE"}, Usage: "auth database file used for access control"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-default-access", Aliases: []string{"p"}, EnvVars: []string{"NTFY_AUTH_DEFAULT_ACCESS"}, Value: "read-write", Usage: "default permissions if no matching entries in the auth database are found"}),
)
var cmdUser = &cli.Command{ var cmdUser = &cli.Command{
Name: "user", Name: "user",
Usage: "Manage/show users", Usage: "Manage/show users",
UsageText: "ntfy user [list|add|remove|change-pass|change-role] ...", UsageText: "ntfy user [list|add|remove|change-pass|change-role] ...",
Flags: flagsUser, Flags: flagsUser,
Before: initConfigFileInputSourceFunc("config", flagsUser), Before: initLogFunc(initConfigFileInputSourceFunc("config", flagsUser)),
Category: categoryServer, Category: categoryServer,
Subcommands: []*cli.Command{ Subcommands: []*cli.Command{
{ {
@ -269,11 +274,3 @@ func readPasswordAndConfirm(c *cli.Context) (string, error) {
} }
return string(password), nil return string(password), nil
} }
func userCommandFlags() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{Name: "config", Aliases: []string{"c"}, EnvVars: []string{"NTFY_CONFIG_FILE"}, Value: "/etc/ntfy/server.yml", DefaultText: "/etc/ntfy/server.yml", Usage: "config file"},
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-file", Aliases: []string{"H"}, EnvVars: []string{"NTFY_AUTH_FILE"}, Usage: "auth database file used for access control"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "auth-default-access", Aliases: []string{"p"}, EnvVars: []string{"NTFY_AUTH_DEFAULT_ACCESS"}, Value: "read-write", Usage: "default permissions if no matching entries in the auth database are found"}),
}
}

79
log/log.go Normal file
View file

@ -0,0 +1,79 @@
package log
import (
"log"
"strings"
)
type Level int
const (
DebugLevel Level = iota
InfoLevel
WarnLevel
ErrorLevel
)
func (l Level) String() string {
switch l {
case DebugLevel:
return "DEBUG"
case InfoLevel:
return "INFO"
case WarnLevel:
return "WARN"
case ErrorLevel:
return "ERROR"
}
return "unknown"
}
var (
level = InfoLevel
)
func Debug(message string, v ...interface{}) {
logIf(DebugLevel, message, v...)
}
func Info(message string, v ...interface{}) {
logIf(InfoLevel, message, v...)
}
func Warn(message string, v ...interface{}) {
logIf(WarnLevel, message, v...)
}
func Error(message string, v ...interface{}) {
logIf(ErrorLevel, message, v...)
}
func Fatal(v ...interface{}) {
log.Fatalln(v...)
}
func SetLevel(newLevel Level) {
level = newLevel
}
func ToLevel(s string) Level {
switch strings.ToLower(s) {
case "debug":
return DebugLevel
case "info":
return InfoLevel
case "warn", "warning":
return WarnLevel
case "error":
return ErrorLevel
default:
log.Fatalf("unknown log level: %s", s)
return 0
}
}
func logIf(l Level, message string, v ...interface{}) {
if level <= l {
log.Printf(l.String()+" "+message, v...)
}
}

View file

@ -9,8 +9,8 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"heckel.io/ntfy/log"
"io" "io"
"log"
"net" "net"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -181,7 +181,7 @@ func (s *Server) Run() error {
if s.config.SMTPServerListen != "" { if s.config.SMTPServerListen != "" {
listenStr += fmt.Sprintf(" %s[smtp]", s.config.SMTPServerListen) listenStr += fmt.Sprintf(" %s[smtp]", s.config.SMTPServerListen)
} }
log.Printf("Listening on%s", listenStr) log.Info("Listening on%s", listenStr)
mux := http.NewServeMux() mux := http.NewServeMux()
mux.HandleFunc("/", s.handle) mux.HandleFunc("/", s.handle)
errChan := make(chan error) errChan := make(chan error)
@ -221,7 +221,7 @@ func (s *Server) Run() error {
} }
s.mu.Unlock() s.mu.Unlock()
go s.runManager() go s.runManager()
go s.runAtSender() go s.runDelaySender()
go s.runFirebaseKeepaliver() go s.runFirebaseKeepaliver()
return <-errChan return <-errChan
@ -248,16 +248,18 @@ func (s *Server) Stop() {
func (s *Server) handle(w http.ResponseWriter, r *http.Request) { func (s *Server) handle(w http.ResponseWriter, r *http.Request) {
v := s.visitor(r) v := s.visitor(r)
log.Debug("[%s] %s %s", v.ip, r.Method, r.URL.Path)
if err := s.handleInternal(w, r, v); err != nil { if err := s.handleInternal(w, r, v); err != nil {
if websocket.IsWebSocketUpgrade(r) { if websocket.IsWebSocketUpgrade(r) {
log.Printf("[%s] WS %s %s - %s", v.ip, r.Method, r.URL.Path, err.Error()) log.Info("[%s] WS %s %s - %s", v.ip, r.Method, r.URL.Path, err.Error())
return // Do not attempt to write to upgraded connection return // Do not attempt to write to upgraded connection
} }
httpErr, ok := err.(*errHTTP) httpErr, ok := err.(*errHTTP)
if !ok { if !ok {
httpErr = errHTTPInternalError httpErr = errHTTPInternalError
} }
log.Printf("[%s] HTTP %s %s - %d - %d - %s", v.ip, r.Method, r.URL.Path, httpErr.HTTPCode, httpErr.Code, err.Error()) log.Info("[%s] HTTP %s %s - %d - %d - %s", v.ip, r.Method, r.URL.Path, httpErr.HTTPCode, httpErr.Code, err.Error())
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "*") // CORS, allow cross-origin requests w.Header().Set("Access-Control-Allow-Origin", "*") // CORS, allow cross-origin requests
w.WriteHeader(httpErr.HTTPCode) w.WriteHeader(httpErr.HTTPCode)
@ -434,6 +436,8 @@ func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request, v *visito
m.Message = emptyMessageBody m.Message = emptyMessageBody
} }
delayed := m.Time > time.Now().Unix() delayed := m.Time > time.Now().Unix()
log.Debug("[%s] %s %s: ev=%s, body=%d bytes, delayed=%t, fb=%t, cache=%t, up=%t, email=%s",
v.ip, r.Method, r.URL.Path, m.Event, len(body.PeekedBytes), delayed, firebase, cache, unifiedpush, email)
if !delayed { if !delayed {
if err := t.Publish(m); err != nil { if err := t.Publish(m); err != nil {
return err return err
@ -466,13 +470,13 @@ func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request, v *visito
func (s *Server) sendToFirebase(v *visitor, m *message) { func (s *Server) sendToFirebase(v *visitor, m *message) {
if err := s.firebase(m); err != nil { if err := s.firebase(m); err != nil {
log.Printf("[%s] FB - Unable to publish to Firebase: %v", v.ip, err.Error()) log.Warn("[%s] FB - Unable to publish to Firebase: %v", v.ip, err.Error())
} }
} }
func (s *Server) sendEmail(v *visitor, m *message, email string) { func (s *Server) sendEmail(v *visitor, m *message, email string) {
if err := s.mailer.Send(v.ip, email, m); err != nil { if err := s.mailer.Send(v.ip, email, m); err != nil {
log.Printf("[%s] MAIL - Unable to send email: %v", v.ip, err.Error()) log.Warn("[%s] MAIL - Unable to send email: %v", v.ip, err.Error())
} }
} }
@ -482,16 +486,16 @@ func (s *Server) forwardPollRequest(v *visitor, m *message) {
forwardURL := fmt.Sprintf("%s/%s", s.config.UpstreamBaseURL, topicHash) forwardURL := fmt.Sprintf("%s/%s", s.config.UpstreamBaseURL, topicHash)
req, err := http.NewRequest("POST", forwardURL, strings.NewReader("")) req, err := http.NewRequest("POST", forwardURL, strings.NewReader(""))
if err != nil { if err != nil {
log.Printf("[%s] FWD - Unable to forward poll request: %v", v.ip, err.Error()) log.Warn("[%s] FWD - Unable to forward poll request: %v", v.ip, err.Error())
return return
} }
req.Header.Set("X-Poll-ID", m.ID) req.Header.Set("X-Poll-ID", m.ID)
response, err := http.DefaultClient.Do(req) response, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
log.Printf("[%s] FWD - Unable to forward poll request: %v", v.ip, err.Error()) log.Warn("[%s] FWD - Unable to forward poll request: %v", v.ip, err.Error())
return return
} else if response.StatusCode != http.StatusOK { } else if response.StatusCode != http.StatusOK {
log.Printf("[%s] FWD - Unable to forward poll request, unexpected status: %d", v.ip, response.StatusCode) log.Warn("[%s] FWD - Unable to forward poll request, unexpected status: %d", v.ip, response.StatusCode)
return return
} }
} }
@ -1015,17 +1019,17 @@ func (s *Server) updateStatsAndPrune() {
ids, err := s.messageCache.AttachmentsExpired() ids, err := s.messageCache.AttachmentsExpired()
if err == nil { if err == nil {
if err := s.fileCache.Remove(ids...); err != nil { if err := s.fileCache.Remove(ids...); err != nil {
log.Printf("error while deleting attachments: %s", err.Error()) log.Warn("Error deleting attachments: %s", err.Error())
} }
} else { } else {
log.Printf("error retrieving expired attachments: %s", err.Error()) log.Warn("Error retrieving expired attachments: %s", err.Error())
} }
} }
// Prune message cache // Prune message cache
olderThan := time.Now().Add(-1 * s.config.CacheDuration) olderThan := time.Now().Add(-1 * s.config.CacheDuration)
if err := s.messageCache.Prune(olderThan); err != nil { if err := s.messageCache.Prune(olderThan); err != nil {
log.Printf("error pruning cache: %s", err.Error()) log.Warn("Error pruning cache: %s", err.Error())
} }
// Prune old topics, remove subscriptions without subscribers // Prune old topics, remove subscriptions without subscribers
@ -1034,7 +1038,7 @@ func (s *Server) updateStatsAndPrune() {
subs := t.Subscribers() subs := t.Subscribers()
msgs, err := s.messageCache.MessageCount(t.ID) msgs, err := s.messageCache.MessageCount(t.ID)
if err != nil { if err != nil {
log.Printf("cannot get stats for topic %s: %s", t.ID, err.Error()) log.Warn("Cannot get stats for topic %s: %s", t.ID, err.Error())
continue continue
} }
if msgs == 0 && subs == 0 { if msgs == 0 && subs == 0 {
@ -1052,7 +1056,7 @@ func (s *Server) updateStatsAndPrune() {
} }
// Print stats // Print stats
log.Printf("Stats: %d message(s) published, %d in cache, %d successful mails, %d failed, %d topic(s) active, %d subscriber(s), %d visitor(s)", log.Info("Stats: %d message(s) published, %d in cache, %d successful mails, %d failed, %d topic(s) active, %d subscriber(s), %d visitor(s)",
s.messages, messages, mailSuccess, mailFailure, len(s.topics), subscribers, len(s.visitors)) s.messages, messages, mailSuccess, mailFailure, len(s.topics), subscribers, len(s.visitors))
} }
@ -1096,12 +1100,12 @@ func (s *Server) runManager() {
} }
} }
func (s *Server) runAtSender() { func (s *Server) runDelaySender() {
for { for {
select { select {
case <-time.After(s.config.AtSenderInterval): case <-time.After(s.config.AtSenderInterval):
if err := s.sendDelayedMessages(); err != nil { if err := s.sendDelayedMessages(); err != nil {
log.Printf("error sending scheduled messages: %s", err.Error()) log.Warn("error sending scheduled messages: %s", err.Error())
} }
case <-s.closeChan: case <-s.closeChan:
return return
@ -1117,11 +1121,11 @@ func (s *Server) runFirebaseKeepaliver() {
select { select {
case <-time.After(s.config.FirebaseKeepaliveInterval): case <-time.After(s.config.FirebaseKeepaliveInterval):
if err := s.firebase(newKeepaliveMessage(firebaseControlTopic)); err != nil { if err := s.firebase(newKeepaliveMessage(firebaseControlTopic)); err != nil {
log.Printf("error sending Firebase keepalive message to %s: %s", firebaseControlTopic, err.Error()) log.Info("error sending Firebase keepalive message to %s: %s", firebaseControlTopic, err.Error())
} }
case <-time.After(s.config.FirebasePollInterval): case <-time.After(s.config.FirebasePollInterval):
if err := s.firebase(newKeepaliveMessage(firebasePollTopic)); err != nil { if err := s.firebase(newKeepaliveMessage(firebasePollTopic)); err != nil {
log.Printf("error sending Firebase keepalive message to %s: %s", firebasePollTopic, err.Error()) log.Info("error sending Firebase keepalive message to %s: %s", firebasePollTopic, err.Error())
} }
case <-s.closeChan: case <-s.closeChan:
return return
@ -1140,12 +1144,12 @@ func (s *Server) sendDelayedMessages() error {
t, ok := s.topics[m.Topic] // If no subscribers, just mark message as published t, ok := s.topics[m.Topic] // If no subscribers, just mark message as published
if ok { if ok {
if err := t.Publish(m); err != nil { if err := t.Publish(m); err != nil {
log.Printf("unable to publish message %s to topic %s: %v", m.ID, m.Topic, err.Error()) log.Info("unable to publish message %s to topic %s: %v", m.ID, m.Topic, err.Error())
} }
} }
if s.firebase != nil { // Firebase subscribers may not show up in topics map if s.firebase != nil { // Firebase subscribers may not show up in topics map
if err := s.firebase(m); err != nil { if err := s.firebase(m); err != nil {
log.Printf("unable to publish to Firebase: %v", err.Error()) log.Info("unable to publish to Firebase: %v", err.Error())
} }
} }
if err := s.messageCache.MarkPublished(m); err != nil { if err := s.messageCache.MarkPublished(m); err != nil {
@ -1252,13 +1256,13 @@ func (s *Server) withAuth(next handleFunc, perm auth.Permission) handleFunc {
username, password, ok := extractUserPass(r) username, password, ok := extractUserPass(r)
if ok { if ok {
if user, err = s.auth.Authenticate(username, password); err != nil { if user, err = s.auth.Authenticate(username, password); err != nil {
log.Printf("authentication failed: %s", err.Error()) log.Info("authentication failed: %s", err.Error())
return errHTTPUnauthorized return errHTTPUnauthorized
} }
} }
for _, t := range topics { for _, t := range topics {
if err := s.auth.Authorize(user, t.ID, perm); err != nil { if err := s.auth.Authorize(user, t.ID, perm); err != nil {
log.Printf("unauthorized: %s", err.Error()) log.Info("unauthorized: %s", err.Error())
return errHTTPForbidden return errHTTPForbidden
} }
} }