Logging in subscribe and publish command

pull/284/head
Philipp Heckel 2022-06-02 11:59:22 -04:00
parent 5cc0b194d3
commit e12995e218
10 changed files with 67 additions and 48 deletions

View File

@ -7,9 +7,9 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"heckel.io/ntfy/log"
"heckel.io/ntfy/util" "heckel.io/ntfy/util"
"io" "io"
"log"
"net/http" "net/http"
"strings" "strings"
"sync" "sync"
@ -102,6 +102,7 @@ func (c *Client) PublishReader(topic string, body io.Reader, options ...PublishO
return nil, err return nil, err
} }
} }
log.Debug("%s Publishing message with headers %s", util.ShortTopicURL(topicURL), req.Header)
resp, err := http.DefaultClient.Do(req) resp, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
return nil, err return nil, err
@ -136,6 +137,7 @@ func (c *Client) Poll(topic string, options ...SubscribeOption) ([]*Message, err
msgChan := make(chan *Message) msgChan := make(chan *Message)
errChan := make(chan error) errChan := make(chan error)
topicURL := c.expandTopicURL(topic) topicURL := c.expandTopicURL(topic)
log.Debug("%s Polling from topic", util.ShortTopicURL(topicURL))
options = append(options, WithPoll()) options = append(options, WithPoll())
go func() { go func() {
err := performSubscribeRequest(ctx, msgChan, topicURL, "", options...) err := performSubscribeRequest(ctx, msgChan, topicURL, "", options...)
@ -171,6 +173,7 @@ func (c *Client) Subscribe(topic string, options ...SubscribeOption) string {
defer c.mu.Unlock() defer c.mu.Unlock()
subscriptionID := util.RandomString(10) subscriptionID := util.RandomString(10)
topicURL := c.expandTopicURL(topic) topicURL := c.expandTopicURL(topic)
log.Debug("%s Subscribing to topic", util.ShortTopicURL(topicURL))
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
c.subscriptions[subscriptionID] = &subscription{ c.subscriptions[subscriptionID] = &subscription{
ID: subscriptionID, ID: subscriptionID,
@ -226,11 +229,11 @@ func handleSubscribeConnLoop(ctx context.Context, msgChan chan *Message, topicUR
// TODO The retry logic is crude and may lose messages. It should record the last message like the // TODO The retry logic is crude and may lose messages. It should record the last message like the
// Android client, use since=, and do incremental backoff too // Android client, use since=, and do incremental backoff too
if err := performSubscribeRequest(ctx, msgChan, topicURL, subcriptionID, options...); err != nil { if err := performSubscribeRequest(ctx, msgChan, topicURL, subcriptionID, options...); err != nil {
log.Printf("Connection to %s failed: %s", topicURL, err.Error()) log.Warn("%s Connection failed: %s", util.ShortTopicURL(topicURL), err.Error())
} }
select { select {
case <-ctx.Done(): case <-ctx.Done():
log.Printf("Connection to %s exited", topicURL) log.Info("Connection exited", util.ShortTopicURL(topicURL))
return return
case <-time.After(10 * time.Second): // TODO Add incremental backoff case <-time.After(10 * time.Second): // TODO Add incremental backoff
} }
@ -238,7 +241,9 @@ func handleSubscribeConnLoop(ctx context.Context, msgChan chan *Message, topicUR
} }
func performSubscribeRequest(ctx context.Context, msgChan chan *Message, topicURL string, subscriptionID string, options ...SubscribeOption) error { func performSubscribeRequest(ctx context.Context, msgChan chan *Message, topicURL string, subscriptionID string, options ...SubscribeOption) error {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/json", topicURL), nil) streamURL := fmt.Sprintf("%s/json", topicURL)
log.Debug("%s Listening to %s", util.ShortTopicURL(topicURL), streamURL)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, streamURL, nil)
if err != nil { if err != nil {
return err return err
} }
@ -261,10 +266,12 @@ func performSubscribeRequest(ctx context.Context, msgChan chan *Message, topicUR
} }
scanner := bufio.NewScanner(resp.Body) scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() { for scanner.Scan() {
m, err := toMessage(scanner.Text(), topicURL, subscriptionID) messageJSON := scanner.Text()
m, err := toMessage(messageJSON, topicURL, subscriptionID)
if err != nil { if err != nil {
return err return err
} }
log.Trace("%s Message received: %s", util.ShortTopicURL(topicURL), messageJSON)
if m.Event == MessageEvent { if m.Event == MessageEvent {
msgChan <- m msgChan <- m
} }

View File

@ -5,12 +5,13 @@ import (
"fmt" "fmt"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"heckel.io/ntfy/client" "heckel.io/ntfy/client"
"heckel.io/ntfy/log"
"heckel.io/ntfy/util" "heckel.io/ntfy/util"
"log"
"os" "os"
"os/exec" "os/exec"
"os/user" "os/user"
"path/filepath" "path/filepath"
"sort"
"strings" "strings"
) )
@ -32,7 +33,6 @@ var flagsSubscribe = append(
&cli.BoolFlag{Name: "from-config", Aliases: []string{"C"}, Usage: "read subscriptions from config file (service mode)"}, &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: "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: "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{
@ -190,6 +190,7 @@ func doSubscribe(c *cli.Context, cl *client.Client, conf *client.Config, topic,
if !ok { if !ok {
continue continue
} }
log.Debug("%s Dispatching received message: %s", logMessagePrefix(m), m.Raw)
printMessageOrRunCommand(c, m, cmd) printMessageOrRunCommand(c, m, cmd)
} }
return nil return nil
@ -199,26 +200,26 @@ func printMessageOrRunCommand(c *cli.Context, m *client.Message, command string)
if command != "" { if command != "" {
runCommand(c, command, m) runCommand(c, command, m)
} else { } else {
log.Debug("%s Printing raw message", logMessagePrefix(m))
fmt.Fprintln(c.App.Writer, m.Raw) fmt.Fprintln(c.App.Writer, m.Raw)
} }
} }
func runCommand(c *cli.Context, command string, m *client.Message) { func runCommand(c *cli.Context, command string, m *client.Message) {
if err := runCommandInternal(c, command, m); err != nil { if err := runCommandInternal(c, command, m); err != nil {
fmt.Fprintf(c.App.ErrWriter, "Command failed: %s\n", err.Error()) log.Warn("%s Command failed: %s", logMessagePrefix(m), err.Error())
} }
} }
func runCommandInternal(c *cli.Context, script string, m *client.Message) error { func runCommandInternal(c *cli.Context, script string, m *client.Message) error {
scriptFile := fmt.Sprintf("%s/ntfy-subscribe-%s.%s", os.TempDir(), util.RandomString(10), scriptExt) scriptFile := fmt.Sprintf("%s/ntfy-subscribe-%s.%s", os.TempDir(), util.RandomString(10), scriptExt)
if err := os.WriteFile(scriptFile, []byte(scriptHeader+script), 0700); err != nil { log.Debug("%s Running command '%s' via temporary script %s", logMessagePrefix(m), script, scriptFile)
script = scriptHeader + script
if err := os.WriteFile(scriptFile, []byte(script), 0700); err != nil {
return err return err
} }
defer os.Remove(scriptFile) defer os.Remove(scriptFile)
verbose := c.Bool("verbose") log.Debug("%s Executing script %s", logMessagePrefix(m), scriptFile)
if verbose {
log.Printf("[%s] Executing: %s (for message: %s)", util.ShortTopicURL(m.TopicURL), script, m.Raw)
}
cmd := exec.Command(scriptLauncher[0], append(scriptLauncher[1:], scriptFile)...) cmd := exec.Command(scriptLauncher[0], append(scriptLauncher[1:], scriptFile)...)
cmd.Stdin = c.App.Reader cmd.Stdin = c.App.Reader
cmd.Stdout = c.App.Writer cmd.Stdout = c.App.Writer
@ -228,7 +229,7 @@ func runCommandInternal(c *cli.Context, script string, m *client.Message) error
} }
func envVars(m *client.Message) []string { func envVars(m *client.Message) []string {
env := os.Environ() env := make([]string, 0)
env = append(env, envVar(m.ID, "NTFY_ID", "id")...) env = append(env, envVar(m.ID, "NTFY_ID", "id")...)
env = append(env, envVar(m.Topic, "NTFY_TOPIC", "topic")...) env = append(env, envVar(m.Topic, "NTFY_TOPIC", "topic")...)
env = append(env, envVar(fmt.Sprintf("%d", m.Time), "NTFY_TIME", "time")...) env = append(env, envVar(fmt.Sprintf("%d", m.Time), "NTFY_TIME", "time")...)
@ -237,7 +238,11 @@ func envVars(m *client.Message) []string {
env = append(env, envVar(fmt.Sprintf("%d", m.Priority), "NTFY_PRIORITY", "priority", "prio", "p")...) env = append(env, envVar(fmt.Sprintf("%d", m.Priority), "NTFY_PRIORITY", "priority", "prio", "p")...)
env = append(env, envVar(strings.Join(m.Tags, ","), "NTFY_TAGS", "tags", "tag", "ta")...) env = append(env, envVar(strings.Join(m.Tags, ","), "NTFY_TAGS", "tags", "tag", "ta")...)
env = append(env, envVar(m.Raw, "NTFY_RAW", "raw")...) env = append(env, envVar(m.Raw, "NTFY_RAW", "raw")...)
return env sort.Strings(env)
if log.IsTrace() {
log.Trace("%s With environment:\n%s", logMessagePrefix(m), strings.Join(env, "\n"))
}
return append(os.Environ(), env...)
} }
func envVar(value string, vars ...string) []string { func envVar(value string, vars ...string) []string {
@ -276,3 +281,7 @@ func defaultClientConfigFileWindows() string {
homeDir, _ := os.UserConfigDir() homeDir, _ := os.UserConfigDir()
return filepath.Join(homeDir, clientUserConfigFileWindowsRelative) return filepath.Join(homeDir, clientUserConfigFileWindowsRelative)
} }
func logMessagePrefix(m *client.Message) string {
return fmt.Sprintf("%s/%s", util.ShortTopicURL(m.TopicURL), m.ID)
}

View File

@ -833,9 +833,9 @@ contents. The `TRACE` setting will also print the message contents.
Both options are very verbose and should only be enabled in production for short periods of time. Otherwise, Both options are very verbose and should only be enabled in production for short periods of time. Otherwise,
you're going to run out of disk space pretty quickly. you're going to run out of disk space pretty quickly.
You can also hot-reload the `log-level` by sending the `SIGHUP` signal to the process (or by calling You can also hot-reload the `log-level` by sending the `SIGHUP` signal to the process after editing the `server.yml` file.
`systemctl reload ntfy` if it's running inside systemd). You can do so by calling `kill -HUP $(pidof ntfy)`. You can do so by calling `systemctl reload ntfy` (if ntfy is running inside systemd), or by calling `kill -HUP $(pidof ntfy)`.
If succesfull, you'll see something like this: If successful, you'll see something like this:
``` ```
$ ntfy serve $ ntfy serve

View File

@ -44,7 +44,7 @@ fi
## Server-sent messages in your web app ## Server-sent messages in your web app
Just as you can [subscribe to topics in the Web UI](subscribe/web.md), you can use ntfy in your own Just as you can [subscribe to topics in the Web UI](subscribe/web.md), you can use ntfy in your own
web application. Check out the <a href="/example.html">live example</a> or just look the source of this page. web application. Check out the <a href="/example.html">live example</a>.
## Notify on SSH login ## Notify on SSH login
Years ago my home server was broken into. That shook me hard, so every time someone logs into any machine that I Years ago my home server was broken into. That shook me hard, so every time someone logs into any machine that I

View File

@ -10,12 +10,14 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
* Italian (thanks to [@Genio2003](https://hosted.weblate.org/user/Genio2003/)) * Italian (thanks to [@Genio2003](https://hosted.weblate.org/user/Genio2003/))
-->
## ntfy server v1.25.0 (UNRELEASED) ## ntfy server v1.25.0
Released June 2, 2022
**Features:** **Features:**
* Advanced logging, with different log levels and hot reloading of the log level (no ticket) * Advanced logging, with different log levels and hot reloading of the log level ([#284](https://github.com/binwiederhier/ntfy/pull/284))
**Bugs**: **Bugs**:
@ -28,18 +30,16 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
**Documentation**: **Documentation**:
* ⚠️ [Privacy policy](privacy.md) updated to reflect additional debug/tracing feature (no ticket)
* [Examples](examples.md) for [Home Assistant](https://www.home-assistant.io/) ([#282](https://github.com/binwiederhier/ntfy/pull/282), thanks to [@poblabs](https://github.com/poblabs)) * [Examples](examples.md) for [Home Assistant](https://www.home-assistant.io/) ([#282](https://github.com/binwiederhier/ntfy/pull/282), thanks to [@poblabs](https://github.com/poblabs))
* Install instructions for [NixOS/Nix](https://ntfy.sh/docs/install/#nixos-nix) ([#282](https://github.com/binwiederhier/ntfy/pull/282), thanks to [@arjan-s](https://github.com/arjan-s)) * Install instructions for [NixOS/Nix](https://ntfy.sh/docs/install/#nixos-nix) ([#282](https://github.com/binwiederhier/ntfy/pull/282), thanks to [@arjan-s](https://github.com/arjan-s))
* Clarify `poll_request` wording for [iOS push notifications](https://ntfy.sh/docs/config/#ios-instant-notifications) ([#300](https://github.com/binwiederhier/ntfy/issues/300), thanks to [@prabirshrestha](https://github.com/prabirshrestha) for reporting) * Clarify `poll_request` wording for [iOS push notifications](https://ntfy.sh/docs/config/#ios-instant-notifications) ([#300](https://github.com/binwiederhier/ntfy/issues/300), thanks to [@prabirshrestha](https://github.com/prabirshrestha) for reporting)
* Example for using ntfy with docker-compose.yml without root privileges ([#304](https://github.com/binwiederhier/ntfy/pull/304), thanks to [@ksurl](https://github.com/ksurl)) * Example for using ntfy with docker-compose.yml without root privileges ([#304](https://github.com/binwiederhier/ntfy/pull/304), thanks to [@ksurl](https://github.com/ksurl))
**Additional translations:** **Additional translations:**
* Chinese/Simplified (thanks to [@yufei.im](https://hosted.weblate.org/user/yufei.im/)) * Chinese/Simplified (thanks to [@yufei.im](https://hosted.weblate.org/user/yufei.im/))
-->
## ntfy iOS app v1.1 ## ntfy iOS app v1.1
Released May 31, 2022 Released May 31, 2022

View File

@ -6,8 +6,8 @@ import (
"errors" "errors"
"fmt" "fmt"
_ "github.com/mattn/go-sqlite3" // SQLite driver _ "github.com/mattn/go-sqlite3" // SQLite driver
"heckel.io/ntfy/log"
"heckel.io/ntfy/util" "heckel.io/ntfy/util"
"log"
"strings" "strings"
"time" "time"
) )
@ -540,7 +540,7 @@ func setupNewCacheDB(db *sql.DB) error {
} }
func migrateFrom0(db *sql.DB) error { func migrateFrom0(db *sql.DB) error {
log.Print("Migrating cache database schema: from 0 to 1") log.Info("Migrating cache database schema: from 0 to 1")
if _, err := db.Exec(migrate0To1AlterMessagesTableQuery); err != nil { if _, err := db.Exec(migrate0To1AlterMessagesTableQuery); err != nil {
return err return err
} }
@ -554,7 +554,7 @@ func migrateFrom0(db *sql.DB) error {
} }
func migrateFrom1(db *sql.DB) error { func migrateFrom1(db *sql.DB) error {
log.Print("Migrating cache database schema: from 1 to 2") log.Info("Migrating cache database schema: from 1 to 2")
if _, err := db.Exec(migrate1To2AlterMessagesTableQuery); err != nil { if _, err := db.Exec(migrate1To2AlterMessagesTableQuery); err != nil {
return err return err
} }
@ -565,7 +565,7 @@ func migrateFrom1(db *sql.DB) error {
} }
func migrateFrom2(db *sql.DB) error { func migrateFrom2(db *sql.DB) error {
log.Print("Migrating cache database schema: from 2 to 3") log.Info("Migrating cache database schema: from 2 to 3")
if _, err := db.Exec(migrate2To3AlterMessagesTableQuery); err != nil { if _, err := db.Exec(migrate2To3AlterMessagesTableQuery); err != nil {
return err return err
} }
@ -576,7 +576,7 @@ func migrateFrom2(db *sql.DB) error {
} }
func migrateFrom3(db *sql.DB) error { func migrateFrom3(db *sql.DB) error {
log.Print("Migrating cache database schema: from 3 to 4") log.Info("Migrating cache database schema: from 3 to 4")
if _, err := db.Exec(migrate3To4AlterMessagesTableQuery); err != nil { if _, err := db.Exec(migrate3To4AlterMessagesTableQuery); err != nil {
return err return err
} }
@ -587,7 +587,7 @@ func migrateFrom3(db *sql.DB) error {
} }
func migrateFrom4(db *sql.DB) error { func migrateFrom4(db *sql.DB) error {
log.Print("Migrating cache database schema: from 4 to 5") log.Info("Migrating cache database schema: from 4 to 5")
if _, err := db.Exec(migrate4To5AlterMessagesTableQuery); err != nil { if _, err := db.Exec(migrate4To5AlterMessagesTableQuery); err != nil {
return err return err
} }
@ -598,7 +598,7 @@ func migrateFrom4(db *sql.DB) error {
} }
func migrateFrom5(db *sql.DB) error { func migrateFrom5(db *sql.DB) error {
log.Print("Migrating cache database schema: from 5 to 6") log.Info("Migrating cache database schema: from 5 to 6")
if _, err := db.Exec(migrate5To6AlterMessagesTableQuery); err != nil { if _, err := db.Exec(migrate5To6AlterMessagesTableQuery); err != nil {
return err return err
} }
@ -609,7 +609,7 @@ func migrateFrom5(db *sql.DB) error {
} }
func migrateFrom6(db *sql.DB) error { func migrateFrom6(db *sql.DB) error {
log.Print("Migrating cache database schema: from 6 to 7") log.Info("Migrating cache database schema: from 6 to 7")
if _, err := db.Exec(migrate6To7AlterMessagesTableQuery); err != nil { if _, err := db.Exec(migrate6To7AlterMessagesTableQuery); err != nil {
return err return err
} }

View File

@ -253,7 +253,7 @@ func (s *Server) handle(w http.ResponseWriter, r *http.Request) {
if isNormalError { if isNormalError {
log.Debug("%s WebSocket error (this error is okay, it happens a lot): %s", logHTTPPrefix(v, r), err.Error()) log.Debug("%s WebSocket error (this error is okay, it happens a lot): %s", logHTTPPrefix(v, r), err.Error())
} else { } else {
log.Warn("%s WebSocket error: %s", logHTTPPrefix(v, r), err.Error()) log.Info("%s WebSocket error: %s", logHTTPPrefix(v, r), err.Error())
} }
return // Do not attempt to write to upgraded connection return // Do not attempt to write to upgraded connection
} }
@ -446,7 +446,7 @@ func (s *Server) handlePublish(w http.ResponseWriter, r *http.Request, v *visito
log.Debug("%s Received message: event=%s, body=%d byte(s), delayed=%t, firebase=%t, cache=%t, up=%t, email=%s", log.Debug("%s Received message: event=%s, body=%d byte(s), delayed=%t, firebase=%t, cache=%t, up=%t, email=%s",
logMessagePrefix(v, m), m.Event, len(m.Message), delayed, firebase, cache, unifiedpush, email) logMessagePrefix(v, m), m.Event, len(m.Message), delayed, firebase, cache, unifiedpush, email)
if log.IsTrace() { if log.IsTrace() {
log.Trace("%s Message body: %s", logMessagePrefix(v, m), maybeMarshalJSON(m)) log.Trace("%s Message body: %s", logMessagePrefix(v, m), util.MaybeMarshalJSON(m))
} }
if !delayed { if !delayed {
if err := t.Publish(v, m); err != nil { if err := t.Publish(v, m); err != nil {

View File

@ -10,6 +10,7 @@ import (
"google.golang.org/api/option" "google.golang.org/api/option"
"heckel.io/ntfy/auth" "heckel.io/ntfy/auth"
"heckel.io/ntfy/log" "heckel.io/ntfy/log"
"heckel.io/ntfy/util"
"strings" "strings"
) )
@ -45,7 +46,7 @@ func (c *firebaseClient) Send(v *visitor, m *message) error {
return err return err
} }
if log.IsTrace() { if log.IsTrace() {
log.Trace("%s Firebase message: %s", logMessagePrefix(v, m), maybeMarshalJSON(fbm)) log.Trace("%s Firebase message: %s", logMessagePrefix(v, m), util.MaybeMarshalJSON(fbm))
} }
err = c.sender.Send(fbm) err = c.sender.Send(fbm)
if err == errFirebaseQuotaExceeded { if err == errFirebaseQuotaExceeded {

View File

@ -1,7 +1,6 @@
package server package server
import ( import (
"encoding/json"
"fmt" "fmt"
"github.com/emersion/go-smtp" "github.com/emersion/go-smtp"
"net/http" "net/http"
@ -59,14 +58,3 @@ func logHTTPPrefix(v *visitor, r *http.Request) string {
func logSMTPPrefix(state *smtp.ConnectionState) string { func logSMTPPrefix(state *smtp.ConnectionState) string {
return fmt.Sprintf("%s/%s SMTP", state.Hostname, state.RemoteAddr.String()) return fmt.Sprintf("%s/%s SMTP", state.Hostname, state.RemoteAddr.String())
} }
func maybeMarshalJSON(v interface{}) string {
messageJSON, err := json.MarshalIndent(v, "", " ")
if err != nil {
return "<cannot serialize>"
}
if len(messageJSON) > 5000 {
return string(messageJSON)[:5000]
}
return string(messageJSON)
}

View File

@ -2,6 +2,7 @@ package util
import ( import (
"encoding/base64" "encoding/base64"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/gabriel-vasile/mimetype" "github.com/gabriel-vasile/mimetype"
@ -264,3 +265,16 @@ func ReadPassword(in io.Reader) ([]byte, error) {
func BasicAuth(user, pass string) string { func BasicAuth(user, pass string) string {
return fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", user, pass)))) return fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", user, pass))))
} }
// MaybeMarshalJSON returns a JSON string of the given object, or "<cannot serialize>" if serialization failed.
// This is useful for logging purposes where a failure doesn't matter that much.
func MaybeMarshalJSON(v interface{}) string {
jsonBytes, err := json.MarshalIndent(v, "", " ")
if err != nil {
return "<cannot serialize>"
}
if len(jsonBytes) > 5000 {
return string(jsonBytes)[:5000]
}
return string(jsonBytes)
}