mirror of https://github.com/jlelse/GoBlog
Use slog
This commit is contained in:
parent
5220c497cf
commit
ad7536034a
|
@ -13,7 +13,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -157,7 +156,7 @@ func (a *goBlog) apCheckMentions(p *post) {
|
|||
links, err := allLinksFromHTML(pr, a.fullPostURL(p))
|
||||
_ = pr.CloseWithError(err)
|
||||
if err != nil {
|
||||
log.Println("Failed to extract links from post: " + err.Error())
|
||||
a.error("ActivityPub: Failed to extract links from post", err)
|
||||
return
|
||||
}
|
||||
apc := a.apHttpClients[p.Blog]
|
||||
|
@ -486,12 +485,12 @@ func (a *goBlog) apUndelete(p *post) {
|
|||
|
||||
func (a *goBlog) apAccept(blogName string, blog *configBlog, follow *ap.Activity) {
|
||||
newFollower := follow.Actor.GetLink()
|
||||
log.Println("New follow request from follower id:", newFollower.String())
|
||||
a.info("AcitivyPub: New follow request from follower", "id", newFollower.String())
|
||||
// Get remote actor
|
||||
follower, err := a.apGetRemoteActor(newFollower, blogName)
|
||||
if err != nil || follower == nil {
|
||||
// Couldn't retrieve remote actor info
|
||||
log.Println("Failed to retrieve remote actor info:", newFollower)
|
||||
a.error("ActivityPub: Failed to retrieve remote actor info", "actor", newFollower)
|
||||
return
|
||||
}
|
||||
// Add or update follower
|
||||
|
@ -529,7 +528,7 @@ func (a *goBlog) apSendProfileUpdates() {
|
|||
func (a *goBlog) apSendToAllFollowers(blog string, activity *ap.Activity, mentions ...string) {
|
||||
inboxes, err := a.db.apGetAllInboxes(blog)
|
||||
if err != nil {
|
||||
log.Println("Failed to retrieve follower inboxes:", err.Error())
|
||||
a.error("ActivityPub: Failed to retrieve follower inboxes", "err", err)
|
||||
return
|
||||
}
|
||||
for _, m := range mentions {
|
||||
|
@ -583,7 +582,7 @@ func (a *goBlog) loadActivityPubPrivateKey() error {
|
|||
if keyData, err := a.db.retrievePersistentCache("activitypub_key"); err == nil && keyData != nil {
|
||||
privateKeyDecoded, _ := pem.Decode(keyData)
|
||||
if privateKeyDecoded == nil {
|
||||
log.Println("failed to decode cached private key")
|
||||
a.error("ActivityPub: failed to decode cached private key")
|
||||
// continue
|
||||
} else {
|
||||
key, err := x509.ParsePKCS1PrivateKey(privateKeyDecoded.Bytes)
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"encoding/gob"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
|
@ -26,7 +25,7 @@ func (a *goBlog) initAPSendQueue() {
|
|||
a.listenOnQueue("ap", 30*time.Second, func(qi *queueItem, dequeue func(), reschedule func(time.Duration)) {
|
||||
var r apRequest
|
||||
if err := gob.NewDecoder(bytes.NewReader(qi.content)).Decode(&r); err != nil {
|
||||
log.Println("activitypub queue:", err.Error())
|
||||
a.error("Activitypub queue", "err", err)
|
||||
dequeue()
|
||||
return
|
||||
}
|
||||
|
@ -40,7 +39,7 @@ func (a *goBlog) initAPSendQueue() {
|
|||
bufferpool.Put(buf)
|
||||
return
|
||||
}
|
||||
log.Println("AP request failed for the 20th time:", r.To)
|
||||
a.info("AP request failed for the 20th time", "to", r.To)
|
||||
_ = a.db.apRemoveInbox(r.To)
|
||||
}
|
||||
dequeue()
|
||||
|
|
6
app.go
6
app.go
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
|
@ -66,8 +67,11 @@ type goBlog struct {
|
|||
inLoad sync.Once
|
||||
// IndieAuth
|
||||
ias *indieauth.Server
|
||||
// Logs
|
||||
// Logs (HTTP)
|
||||
logf *rotatelogs.RotateLogs
|
||||
// Logs (Program)
|
||||
logger *slog.Logger
|
||||
logLevel *slog.LevelVar
|
||||
// Markdown
|
||||
md, absoluteMd, titleMd goldmark.Markdown
|
||||
// Media
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
@ -25,7 +24,7 @@ func (a *goBlog) serveBlogroll(w http.ResponseWriter, r *http.Request) {
|
|||
return a.getBlogrollOutlines(blog)
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Failed to get outlines: %v", err)
|
||||
a.error("Blogroll: Failed to get outlines", "err", err)
|
||||
a.serveError(w, r, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
@ -48,7 +47,7 @@ func (a *goBlog) serveBlogrollExport(w http.ResponseWriter, r *http.Request) {
|
|||
return a.getBlogrollOutlines(blog)
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Failed to get outlines: %v", err)
|
||||
a.error("Blogroll: Failed to get outlines", "err", err)
|
||||
a.serveError(w, r, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
3
cache.go
3
cache.go
|
@ -2,7 +2,6 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
|
@ -41,7 +40,7 @@ func (a *goBlog) initCache() (err error) {
|
|||
ticker := time.NewTicker(15 * time.Minute)
|
||||
for range ticker.C {
|
||||
met := a.cache.c.Metrics
|
||||
log.Println("\nCache:", met.String())
|
||||
a.info("Cache metrics", "metrics", met.String())
|
||||
}
|
||||
}()
|
||||
return
|
||||
|
|
|
@ -3,7 +3,6 @@ package main
|
|||
import (
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
|
@ -104,8 +103,6 @@ func Test_captchaMiddleware(t *testing.T) {
|
|||
_, captchaSolved := session.Values["captcha"].(bool)
|
||||
assert.False(t, captchaSolved)
|
||||
|
||||
log.Println("Captcha ID:", captchaId)
|
||||
|
||||
// Check form values
|
||||
doc, err := goquery.NewDocumentFromReader(res.Body)
|
||||
_ = res.Body.Close()
|
||||
|
@ -157,8 +154,6 @@ func Test_captchaMiddleware(t *testing.T) {
|
|||
_, captchaSolved = session.Values["captcha"].(bool)
|
||||
assert.False(t, captchaSolved)
|
||||
|
||||
log.Println("Captcha ID:", captchaId)
|
||||
|
||||
// Check form values
|
||||
doc, err = goquery.NewDocumentFromReader(res.Body)
|
||||
_ = res.Body.Close()
|
||||
|
|
14
check.go
14
check.go
|
@ -4,7 +4,6 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
@ -27,24 +26,24 @@ func (a *goBlog) checkAllExternalLinks() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return a.checkLinks(log.Writer(), posts...)
|
||||
return a.checkLinks(posts...)
|
||||
}
|
||||
|
||||
func (a *goBlog) checkLinks(w io.Writer, posts ...*post) error {
|
||||
func (a *goBlog) checkLinks(posts ...*post) error {
|
||||
// Get all links
|
||||
allLinks, err := a.allLinksToCheck(posts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Print some info
|
||||
fmt.Fprintln(w, "Checking", len(allLinks), "links")
|
||||
fmt.Println("Checking", len(allLinks), "links")
|
||||
// Cancel context
|
||||
cancelContext, cancelFunc := context.WithCancel(context.Background())
|
||||
var done atomic.Bool
|
||||
a.shutdown.Add(func() {
|
||||
done.Store(true)
|
||||
cancelFunc()
|
||||
fmt.Fprintln(w, "Cancelled link check")
|
||||
fmt.Println("Cancelled link check")
|
||||
})
|
||||
// Create HTTP cache
|
||||
cache, err := ristretto.NewCache(&ristretto.Config{
|
||||
|
@ -106,11 +105,12 @@ func (a *goBlog) checkLinks(w io.Writer, posts ...*post) error {
|
|||
continue
|
||||
}
|
||||
if r.err != nil {
|
||||
fmt.Fprintf(w, "%s in %s: %s\n", r.link, r.in, r.err.Error())
|
||||
fmt.Printf("%s in %s: %s\n", r.link, r.in, r.err.Error())
|
||||
} else if !successStatus(r.status) {
|
||||
fmt.Fprintf(w, "%s in %s: %d (%s)\n", r.link, r.in, r.status, http.StatusText(r.status))
|
||||
fmt.Printf("%s in %s: %d (%s)\n", r.link, r.in, r.status, http.StatusText(r.status))
|
||||
}
|
||||
}
|
||||
fmt.Println("Finished link check")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ package main
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
@ -386,6 +385,8 @@ func (a *goBlog) initConfig(logging bool) error {
|
|||
if a.cfg.initialized {
|
||||
return nil
|
||||
}
|
||||
// Update log level
|
||||
a.updateLogLevel()
|
||||
// Init database
|
||||
if err := a.initDatabase(logging); err != nil {
|
||||
return err
|
||||
|
@ -561,7 +562,7 @@ func (a *goBlog) initConfig(logging bool) error {
|
|||
}
|
||||
// Log success
|
||||
a.cfg.initialized = true
|
||||
log.Println("Initialized configuration")
|
||||
a.info("Initialized configuration")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
|
@ -58,7 +57,7 @@ func (a *goBlog) sendContactSubmission(w http.ResponseWriter, r *http.Request) {
|
|||
// Send submission
|
||||
go func() {
|
||||
if err := a.sendContactEmail(bc.Contact, message.String(), formEmail); err != nil {
|
||||
log.Println(err.Error())
|
||||
a.error("Failed to send contact email", "err", err)
|
||||
}
|
||||
}()
|
||||
// Send notification
|
||||
|
|
19
database.go
19
database.go
|
@ -4,7 +4,6 @@ import (
|
|||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -17,6 +16,7 @@ import (
|
|||
)
|
||||
|
||||
type database struct {
|
||||
a *goBlog
|
||||
// Basic things
|
||||
db *sql.DB // database
|
||||
em sync.Mutex // command execution (insert, update, delete ...)
|
||||
|
@ -35,7 +35,7 @@ func (a *goBlog) initDatabase(logging bool) (err error) {
|
|||
return
|
||||
}
|
||||
if logging {
|
||||
log.Println("Initialize database...")
|
||||
a.info("Initialize database")
|
||||
}
|
||||
// Setup db
|
||||
db, err := a.openDatabase(a.cfg.Db.File, logging)
|
||||
|
@ -46,9 +46,9 @@ func (a *goBlog) initDatabase(logging bool) (err error) {
|
|||
a.db = db
|
||||
a.shutdown.Add(func() {
|
||||
if err := db.close(); err != nil {
|
||||
log.Printf("Failed to close database: %v", err)
|
||||
a.error("Failed to close database", "err", err)
|
||||
} else {
|
||||
log.Println("Closed database")
|
||||
a.info("Closed database")
|
||||
}
|
||||
})
|
||||
if a.cfg.Db.DumpFile != "" {
|
||||
|
@ -58,7 +58,7 @@ func (a *goBlog) initDatabase(logging bool) (err error) {
|
|||
db.dump(a.cfg.Db.DumpFile)
|
||||
}
|
||||
if logging {
|
||||
log.Println("Initialized database")
|
||||
a.info("Initialized database")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -116,7 +116,7 @@ func (a *goBlog) openDatabase(file string, logging bool) (*database, error) {
|
|||
return nil, errors.New("sqlite not compiled with FTS5")
|
||||
}
|
||||
// Migrate DB
|
||||
err = migrateDb(db, logging)
|
||||
err = a.migrateDb(db, logging)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -144,6 +144,7 @@ func (a *goBlog) openDatabase(file string, logging bool) (*database, error) {
|
|||
return nil, err
|
||||
}
|
||||
return &database{
|
||||
a: a,
|
||||
db: db,
|
||||
debug: debug,
|
||||
psc: psc,
|
||||
|
@ -163,11 +164,11 @@ func (db *database) dump(file string) {
|
|||
// Dump database
|
||||
f, err := os.Create(file)
|
||||
if err != nil {
|
||||
log.Println("Error while dump db:", err.Error())
|
||||
db.a.error("Error while dump db", "err", err)
|
||||
return
|
||||
}
|
||||
if err = sqlite3dump.DumpDB(db.db, f, sqlite3dump.WithTransaction(true)); err != nil {
|
||||
log.Println("Error while dump db:", err.Error())
|
||||
db.a.error("Error while dump db", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -202,7 +203,7 @@ func (db *database) prepare(query string, args ...any) (*sql.Stmt, []any, error)
|
|||
})
|
||||
if err != nil {
|
||||
if db.debug {
|
||||
log.Printf(`Failed to prepare query "%s": %s`, query, err.Error())
|
||||
db.a.error("Failed to prepare query", "query", query, "err", err)
|
||||
}
|
||||
return nil, args, err
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
|
@ -25,34 +24,27 @@ func (db *database) dbAfter(ctx context.Context, query string, args ...any) {
|
|||
return
|
||||
}
|
||||
dur := time.Since(ctx.Value(dbHooksBegin).(time.Time))
|
||||
logBuilder := builderpool.Get()
|
||||
logBuilder.WriteString("\nQuery: ")
|
||||
logBuilder.WriteString(`"`)
|
||||
logBuilder.WriteString(query)
|
||||
logBuilder.WriteString(`"`)
|
||||
argsBuilder := builderpool.Get()
|
||||
if len(args) > 0 {
|
||||
logBuilder.WriteString("\nArgs: ")
|
||||
for i, arg := range args {
|
||||
if i > 0 {
|
||||
logBuilder.WriteString(", ")
|
||||
argsBuilder.WriteString(", ")
|
||||
}
|
||||
if named, ok := arg.(sql.NamedArg); ok && named.Name != "" {
|
||||
logBuilder.WriteString("(")
|
||||
logBuilder.WriteString(named.Name)
|
||||
logBuilder.WriteString(`) "`)
|
||||
logBuilder.WriteString(argToString(named.Value))
|
||||
logBuilder.WriteString(`"`)
|
||||
argsBuilder.WriteString("(")
|
||||
argsBuilder.WriteString(named.Name)
|
||||
argsBuilder.WriteString(`) '`)
|
||||
argsBuilder.WriteString(argToString(named.Value))
|
||||
argsBuilder.WriteString(`'`)
|
||||
} else {
|
||||
logBuilder.WriteString(`"`)
|
||||
logBuilder.WriteString(argToString(arg))
|
||||
logBuilder.WriteString(`"`)
|
||||
argsBuilder.WriteString(`'`)
|
||||
argsBuilder.WriteString(argToString(arg))
|
||||
argsBuilder.WriteString(`'`)
|
||||
}
|
||||
}
|
||||
}
|
||||
logBuilder.WriteString("\nDuration: ")
|
||||
logBuilder.WriteString(dur.String())
|
||||
log.Println(logBuilder.String())
|
||||
builderpool.Put(logBuilder)
|
||||
db.a.debug("Database query", "query", query, "args", argsBuilder.String(), "duration", dur.String())
|
||||
builderpool.Put(argsBuilder)
|
||||
}
|
||||
|
||||
func argToString(arg any) string {
|
||||
|
|
|
@ -3,8 +3,8 @@ package main
|
|||
import (
|
||||
"database/sql"
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/lopezator/migrator"
|
||||
|
@ -13,7 +13,7 @@ import (
|
|||
//go:embed dbmigrations/*
|
||||
var dbMigrations embed.FS
|
||||
|
||||
func migrateDb(db *sql.DB, logging bool) error {
|
||||
func (a *goBlog) migrateDb(db *sql.DB, logging bool) error {
|
||||
var sqlMigrations []any
|
||||
err := fs.WalkDir(dbMigrations, "dbmigrations", func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil || d.Type().IsDir() {
|
||||
|
@ -41,7 +41,7 @@ func migrateDb(db *sql.DB, logging bool) error {
|
|||
m, err := migrator.New(
|
||||
migrator.WithLogger(migrator.LoggerFunc(func(s string, i ...any) {
|
||||
if logging {
|
||||
log.Printf(s, i)
|
||||
a.info(fmt.Sprintf(s, i...))
|
||||
}
|
||||
})),
|
||||
migrator.Migrations(sqlMigrations...),
|
||||
|
|
9
debug.go
9
debug.go
|
@ -1,9 +0,0 @@
|
|||
package main
|
||||
|
||||
import "log"
|
||||
|
||||
func (a *goBlog) debug(msg ...any) {
|
||||
if a.cfg.Debug {
|
||||
log.Println(append([]any{"Debug:"}, msg...)...)
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@ package main
|
|||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log"
|
||||
"math"
|
||||
|
||||
"github.com/tkrajina/gpxgo/gpx"
|
||||
|
@ -51,7 +50,7 @@ func (a *goBlog) getTrack(p *post, withMapFeatures bool) (result *trackResult, e
|
|||
parseResult, err := trackParseGPX(gpxString)
|
||||
if err != nil {
|
||||
// Failed to parse, but just log error
|
||||
log.Printf("failed to parse GPX: %v", err)
|
||||
a.error("failed to parse GPX", "err", err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
|
34
hooks.go
34
hooks.go
|
@ -2,7 +2,6 @@ package main
|
|||
|
||||
import (
|
||||
"html/template"
|
||||
"log"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
|
@ -14,7 +13,7 @@ func (a *goBlog) preStartHooks() {
|
|||
cfg := a.cfg.Hooks
|
||||
for _, cmd := range cfg.PreStart {
|
||||
func(cmd string) {
|
||||
executeHookCommand("pre-start", cfg.Shell, cmd)
|
||||
a.executeHookCommand("pre-start", cfg.Shell, cmd)
|
||||
}(cmd)
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +25,7 @@ func (a *goBlog) postPostHooks(p *post) {
|
|||
if hc := a.cfg.Hooks; hc != nil {
|
||||
for _, cmdTmplString := range hc.PostPost {
|
||||
go func(p *post, cmdTmplString string) {
|
||||
a.cfg.Hooks.executeTemplateCommand("post-post", cmdTmplString, map[string]any{
|
||||
a.executeHookTemplateCommand("post-post", cmdTmplString, map[string]any{
|
||||
"URL": a.fullPostURL(p),
|
||||
"Post": p,
|
||||
})
|
||||
|
@ -46,7 +45,7 @@ func (a *goBlog) postUpdateHooks(p *post) {
|
|||
if hc := a.cfg.Hooks; hc != nil {
|
||||
for _, cmdTmplString := range hc.PostUpdate {
|
||||
go func(p *post, cmdTmplString string) {
|
||||
a.cfg.Hooks.executeTemplateCommand("post-update", cmdTmplString, map[string]any{
|
||||
a.executeHookTemplateCommand("post-update", cmdTmplString, map[string]any{
|
||||
"URL": a.fullPostURL(p),
|
||||
"Post": p,
|
||||
})
|
||||
|
@ -65,7 +64,7 @@ func (a *goBlog) postDeleteHooks(p *post) {
|
|||
if hc := a.cfg.Hooks; hc != nil {
|
||||
for _, cmdTmplString := range hc.PostDelete {
|
||||
go func(p *post, cmdTmplString string) {
|
||||
a.cfg.Hooks.executeTemplateCommand("post-delete", cmdTmplString, map[string]any{
|
||||
a.executeHookTemplateCommand("post-delete", cmdTmplString, map[string]any{
|
||||
"URL": a.fullPostURL(p),
|
||||
"Post": p,
|
||||
})
|
||||
|
@ -84,7 +83,7 @@ func (a *goBlog) postUndeleteHooks(p *post) {
|
|||
if hc := a.cfg.Hooks; hc != nil {
|
||||
for _, cmdTmplString := range hc.PostUndelete {
|
||||
go func(p *post, cmdTmplString string) {
|
||||
a.cfg.Hooks.executeTemplateCommand("post-undelete", cmdTmplString, map[string]any{
|
||||
a.executeHookTemplateCommand("post-undelete", cmdTmplString, map[string]any{
|
||||
"URL": a.fullPostURL(p),
|
||||
"Post": p,
|
||||
})
|
||||
|
@ -96,19 +95,20 @@ func (a *goBlog) postUndeleteHooks(p *post) {
|
|||
}
|
||||
}
|
||||
|
||||
func (cfg *configHooks) executeTemplateCommand(hookType string, tmpl string, data map[string]any) {
|
||||
func (a *goBlog) executeHookTemplateCommand(hookType string, tmpl string, data map[string]any) {
|
||||
cfg := a.cfg.Hooks
|
||||
cmdTmpl, err := template.New("cmd").Parse(tmpl)
|
||||
if err != nil {
|
||||
log.Println("Failed to parse cmd template:", err.Error())
|
||||
a.error("Failed to parse cmd template", "err", err)
|
||||
return
|
||||
}
|
||||
cmdBuf := bufferpool.Get()
|
||||
defer bufferpool.Put(cmdBuf)
|
||||
if err = cmdTmpl.Execute(cmdBuf, data); err != nil {
|
||||
log.Println("Failed to execute cmd template:", err.Error())
|
||||
a.error("Failed to execute cmd template", "err", err)
|
||||
return
|
||||
}
|
||||
executeHookCommand(hookType, cfg.Shell, cmdBuf.String())
|
||||
a.executeHookCommand(hookType, cfg.Shell, cmdBuf.String())
|
||||
}
|
||||
|
||||
type hourlyHookFunc func()
|
||||
|
@ -119,7 +119,7 @@ func (a *goBlog) startHourlyHooks() {
|
|||
for _, cmd := range cfg.Hourly {
|
||||
c := cmd
|
||||
f := func() {
|
||||
executeHookCommand("hourly", cfg.Shell, c)
|
||||
a.executeHookCommand("hourly", cfg.Shell, c)
|
||||
}
|
||||
a.hourlyHooks = append(a.hourlyHooks, f)
|
||||
}
|
||||
|
@ -135,7 +135,7 @@ func (a *goBlog) startHourlyHooks() {
|
|||
ticker := time.NewTicker(1 * time.Hour)
|
||||
a.shutdown.Add(func() {
|
||||
ticker.Stop()
|
||||
log.Println("Stopped hourly hooks")
|
||||
a.info("Stopped hourly hooks")
|
||||
})
|
||||
for range ticker.C {
|
||||
for _, f := range a.hourlyHooks {
|
||||
|
@ -145,19 +145,19 @@ func (a *goBlog) startHourlyHooks() {
|
|||
})
|
||||
a.shutdown.Add(func() {
|
||||
if tr.Stop() {
|
||||
log.Println("Canceled hourly hooks")
|
||||
a.info("Canceled hourly hooks")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func executeHookCommand(hookType, shell, cmd string) {
|
||||
log.Printf("Executing %v hook: %v", hookType, cmd)
|
||||
func (a *goBlog) executeHookCommand(hookType, shell, cmd string) {
|
||||
a.info("Executing hook", "type", hookType, "cmd", cmd)
|
||||
out, err := exec.Command(shell, "-c", cmd).CombinedOutput()
|
||||
if err != nil {
|
||||
log.Println("Failed to execute command:", err.Error())
|
||||
a.error("Failed to execute command", "err", err, "cmd", cmd)
|
||||
}
|
||||
if len(out) > 0 {
|
||||
log.Printf("Output:\n%v", string(out))
|
||||
a.info("Hook output", "out", string(out))
|
||||
}
|
||||
}
|
||||
|
|
17
http.go
17
http.go
|
@ -4,7 +4,6 @@ import (
|
|||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
@ -33,7 +32,7 @@ const (
|
|||
)
|
||||
|
||||
func (a *goBlog) startServer() (err error) {
|
||||
log.Println("Start server(s)...")
|
||||
a.info("Start server(s)...")
|
||||
// Load router
|
||||
a.reloadRouter()
|
||||
// Set basic middlewares
|
||||
|
@ -63,7 +62,7 @@ func (a *goBlog) startServer() (err error) {
|
|||
if a.cfg.Server.Tor {
|
||||
go func() {
|
||||
if err := a.startOnionService(finalHandler); err != nil {
|
||||
log.Println("Tor failed:", err.Error())
|
||||
a.error("Tor failed", "err", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
@ -82,9 +81,9 @@ func (a *goBlog) startServer() (err error) {
|
|||
ReadTimeout: 5 * time.Minute,
|
||||
WriteTimeout: 5 * time.Minute,
|
||||
}
|
||||
a.shutdown.Add(shutdownServer(httpServer, "http server"))
|
||||
a.shutdown.Add(a.shutdownServer(httpServer, "http server"))
|
||||
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Println("Failed to start HTTP server:", err.Error())
|
||||
a.error("Failed to start HTTP server", "err", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
@ -94,7 +93,7 @@ func (a *goBlog) startServer() (err error) {
|
|||
ReadTimeout: 5 * time.Minute,
|
||||
WriteTimeout: 5 * time.Minute,
|
||||
}
|
||||
a.shutdown.Add(shutdownServer(s, "main server"))
|
||||
a.shutdown.Add(a.shutdownServer(s, "main server"))
|
||||
s.Addr = ":" + strconv.Itoa(a.cfg.Server.Port)
|
||||
if a.cfg.Server.PublicHTTPS {
|
||||
s.TLSConfig = a.getAutocertManager().TLSConfig()
|
||||
|
@ -110,14 +109,14 @@ func (a *goBlog) startServer() (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
func shutdownServer(s *http.Server, name string) func() {
|
||||
func (a *goBlog) shutdownServer(s *http.Server, name string) func() {
|
||||
return func() {
|
||||
toc, c := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer c()
|
||||
if err := s.Shutdown(toc); err != nil {
|
||||
log.Printf("Error on server shutdown (%v): %v", name, err)
|
||||
a.error("Error on server shutdown (%v): %v", name, err)
|
||||
}
|
||||
log.Println("Stopped server:", name)
|
||||
a.info("Stopped server", "name", name)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
11
indexnow.go
11
indexnow.go
|
@ -2,7 +2,6 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/carlmjohnson/requests"
|
||||
|
@ -50,7 +49,7 @@ func (a *goBlog) indexNow(url string) {
|
|||
}
|
||||
key := a.indexNowKey()
|
||||
if len(key) == 0 {
|
||||
log.Println("Skipping IndexNow")
|
||||
a.info("Skipping IndexNow")
|
||||
return
|
||||
}
|
||||
err := requests.URL("https://api.indexnow.org/indexnow").
|
||||
|
@ -59,10 +58,10 @@ func (a *goBlog) indexNow(url string) {
|
|||
Param("key", string(key)).
|
||||
Fetch(context.Background())
|
||||
if err != nil {
|
||||
log.Println("Sending IndexNow request failed:", err.Error())
|
||||
a.error("Sending IndexNow request failed", "err", err)
|
||||
return
|
||||
} else {
|
||||
log.Println("IndexNow request sent for", url)
|
||||
a.info("IndexNow request sent", "url", url)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,7 +70,7 @@ func (a *goBlog) indexNowKey() []byte {
|
|||
// Try to load key from database
|
||||
keyBytes, err := a.db.retrievePersistentCache("indexnowkey")
|
||||
if err != nil {
|
||||
log.Println("Failed to retrieve cached IndexNow key:", err.Error())
|
||||
a.error("Failed to retrieve cached IndexNow key", "err", err)
|
||||
return
|
||||
}
|
||||
if keyBytes == nil {
|
||||
|
@ -80,7 +79,7 @@ func (a *goBlog) indexNowKey() []byte {
|
|||
// Store key in database
|
||||
err = a.db.cachePersistently("indexnowkey", keyBytes)
|
||||
if err != nil {
|
||||
log.Println("Failed to cache IndexNow key:", err.Error())
|
||||
a.error("Failed to cache IndexNow key", "err", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
)
|
||||
|
||||
func (a *goBlog) initLog() {
|
||||
a.logLevel = new(slog.LevelVar)
|
||||
a.logger = slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
||||
Level: a.logLevel,
|
||||
}))
|
||||
}
|
||||
|
||||
func (a *goBlog) updateLogLevel() {
|
||||
if a.logLevel == nil {
|
||||
a.initLog()
|
||||
}
|
||||
if a.cfg.Debug {
|
||||
a.logLevel.Set(slog.LevelDebug)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *goBlog) debug(msg string, args ...any) {
|
||||
a.logger.Debug(msg, args...)
|
||||
}
|
||||
|
||||
func (a *goBlog) info(msg string, args ...any) {
|
||||
a.logger.Info(msg, args...)
|
||||
}
|
||||
|
||||
func (a *goBlog) error(msg string, args ...any) {
|
||||
a.logger.Error(msg, args...)
|
||||
}
|
||||
|
||||
func (a *goBlog) fatal(msg string, args ...any) {
|
||||
a.error(msg, args...)
|
||||
os.Exit(1)
|
||||
}
|
64
main.go
64
main.go
|
@ -2,7 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
netpprof "net/http/pprof"
|
||||
|
@ -22,17 +22,23 @@ func main() {
|
|||
memprofile := flag.String("memprofile", "", "write memory profile to `file`")
|
||||
configfile := flag.String("config", "", "use a specific config file")
|
||||
|
||||
// Init app and logger
|
||||
app := &goBlog{
|
||||
httpClient: newHttpClient(),
|
||||
}
|
||||
app.initLog()
|
||||
|
||||
// Init CPU and memory profiling
|
||||
flag.Parse()
|
||||
if *cpuprofile != "" {
|
||||
f, err := os.Create(*cpuprofile)
|
||||
if err != nil {
|
||||
log.Fatalln("could not create CPU profile: ", err)
|
||||
app.fatal("could not create CPU profile", "err", err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
if err := pprof.StartCPUProfile(f); err != nil {
|
||||
log.Fatalln("could not start CPU profile: ", err)
|
||||
app.fatal("could not start CPU profile", "err", err)
|
||||
return
|
||||
}
|
||||
defer pprof.StopCPUProfile()
|
||||
|
@ -41,29 +47,25 @@ func main() {
|
|||
defer func() {
|
||||
f, err := os.Create(*memprofile)
|
||||
if err != nil {
|
||||
log.Fatalln("could not create memory profile: ", err.Error())
|
||||
app.fatal("could not create memory profile", "err", err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
runtime.GC()
|
||||
if err := pprof.WriteHeapProfile(f); err != nil {
|
||||
log.Fatalln("could not write memory profile: ", err.Error())
|
||||
app.fatal("could not write memory profile", "err", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
app := &goBlog{
|
||||
httpClient: newHttpClient(),
|
||||
}
|
||||
|
||||
// Initialize config
|
||||
if err = app.loadConfigFile(*configfile); err != nil {
|
||||
app.logErrAndQuit("Failed to load config file:", err.Error())
|
||||
app.logErrAndQuit("Failed to load config file", "err", err)
|
||||
return
|
||||
}
|
||||
if err = app.initConfig(false); err != nil {
|
||||
app.logErrAndQuit("Failed to init config:", err.Error())
|
||||
app.logErrAndQuit("Failed to init config", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -83,17 +85,17 @@ func main() {
|
|||
AccountName: app.cfg.User.Nick,
|
||||
})
|
||||
if err != nil {
|
||||
app.logErrAndQuit(err.Error())
|
||||
app.logErrAndQuit("Failed to generate TOTP secret", "err", err)
|
||||
return
|
||||
}
|
||||
log.Println("TOTP-Secret:", key.Secret())
|
||||
fmt.Println("TOTP-Secret:", key.Secret())
|
||||
app.shutdown.ShutdownAndWait()
|
||||
return
|
||||
}
|
||||
|
||||
// Initialize plugins
|
||||
if err = app.initPlugins(); err != nil {
|
||||
app.logErrAndQuit("Failed to init plugins:", err.Error())
|
||||
app.logErrAndQuit("Failed to init plugins", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -119,13 +121,13 @@ func main() {
|
|||
}
|
||||
listener, err := net.Listen("tcp", pprofServer.Addr)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to start pprof server:", err.Error())
|
||||
app.fatal("Failed to start pprof server", "err", err)
|
||||
return
|
||||
}
|
||||
log.Println("Pprof server listening on", listener.Addr().String())
|
||||
app.info("Pprof server listening", "addr", listener.Addr().String())
|
||||
// Start server
|
||||
if err := pprofServer.Serve(listener); err != nil {
|
||||
log.Fatalln("Failed to start pprof server:", err.Error())
|
||||
app.fatal("Failed to start pprof server", "err", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
@ -139,11 +141,11 @@ func main() {
|
|||
app.initMarkdown()
|
||||
err = app.initTemplateStrings()
|
||||
if err != nil {
|
||||
app.logErrAndQuit("Failed to start check:", err.Error())
|
||||
app.logErrAndQuit("Failed to start check", "err", err)
|
||||
}
|
||||
err = app.checkAllExternalLinks()
|
||||
if err != nil {
|
||||
app.logErrAndQuit("Failed to start check:", err.Error())
|
||||
app.logErrAndQuit("Failed to start check", "err", err)
|
||||
}
|
||||
app.shutdown.ShutdownAndWait()
|
||||
return
|
||||
|
@ -157,7 +159,7 @@ func main() {
|
|||
}
|
||||
err = app.exportMarkdownFiles(dir)
|
||||
if err != nil {
|
||||
app.logErrAndQuit("Failed to export markdown files:", err.Error())
|
||||
app.logErrAndQuit("Failed to export markdown files", "err", err)
|
||||
return
|
||||
}
|
||||
app.shutdown.ShutdownAndWait()
|
||||
|
@ -173,7 +175,7 @@ func main() {
|
|||
// Start the server
|
||||
err = app.startServer()
|
||||
if err != nil {
|
||||
app.logErrAndQuit("Failed to start server(s):", err.Error())
|
||||
app.logErrAndQuit("Failed to start server(s)", "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -184,31 +186,31 @@ func main() {
|
|||
func (app *goBlog) initComponents() {
|
||||
var err error
|
||||
|
||||
log.Println("Initialize components...")
|
||||
app.info("Initialize components...")
|
||||
|
||||
app.initMarkdown()
|
||||
if err = app.initTemplateAssets(); err != nil { // Needs minify
|
||||
app.logErrAndQuit("Failed to init template assets:", err.Error())
|
||||
app.logErrAndQuit("Failed to init template assets", "err", err)
|
||||
return
|
||||
}
|
||||
if err = app.initTemplateStrings(); err != nil {
|
||||
app.logErrAndQuit("Failed to init template translations:", err.Error())
|
||||
app.logErrAndQuit("Failed to init template translations", "err", err)
|
||||
return
|
||||
}
|
||||
if err = app.initCache(); err != nil {
|
||||
app.logErrAndQuit("Failed to init HTTP cache:", err.Error())
|
||||
app.logErrAndQuit("Failed to init HTTP cache", "err", err)
|
||||
return
|
||||
}
|
||||
if err = app.initRegexRedirects(); err != nil {
|
||||
app.logErrAndQuit("Failed to init redirects:", err.Error())
|
||||
app.logErrAndQuit("Failed to init redirects", "err", err)
|
||||
return
|
||||
}
|
||||
if err = app.initHTTPLog(); err != nil {
|
||||
app.logErrAndQuit("Failed to init HTTP logging:", err.Error())
|
||||
app.logErrAndQuit("Failed to init HTTP logging", "err", err)
|
||||
return
|
||||
}
|
||||
if err = app.initActivityPub(); err != nil {
|
||||
app.logErrAndQuit("Failed to init ActivityPub:", err.Error())
|
||||
app.logErrAndQuit("Failed to init ActivityPub", "err", err)
|
||||
return
|
||||
}
|
||||
app.initWebmention()
|
||||
|
@ -221,11 +223,11 @@ func (app *goBlog) initComponents() {
|
|||
app.initPostsDeleter()
|
||||
app.initIndexNow()
|
||||
|
||||
log.Println("Initialized components")
|
||||
app.info("Initialized components")
|
||||
}
|
||||
|
||||
func (a *goBlog) logErrAndQuit(v ...any) {
|
||||
log.Println(v...)
|
||||
func (a *goBlog) logErrAndQuit(msg string, args ...any) {
|
||||
a.error(msg, args...)
|
||||
a.shutdown.ShutdownAndWait()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"fmt"
|
||||
"image/png"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/carlmjohnson/requests"
|
||||
|
@ -42,17 +41,18 @@ func (a *goBlog) initMediaCompressors() {
|
|||
}
|
||||
config := a.cfg.Micropub.MediaStorage
|
||||
if key := config.TinifyKey; key != "" {
|
||||
a.compressors = append(a.compressors, &tinify{key})
|
||||
a.compressors = append(a.compressors, &tinify{a: a, key: key})
|
||||
}
|
||||
if config.CloudflareCompressionEnabled {
|
||||
a.compressors = append(a.compressors, &cloudflare{})
|
||||
}
|
||||
if config.LocalCompressionEnabled {
|
||||
a.compressors = append(a.compressors, &localMediaCompressor{})
|
||||
a.compressors = append(a.compressors, &localMediaCompressor{a: a})
|
||||
}
|
||||
}
|
||||
|
||||
type tinify struct {
|
||||
a *goBlog
|
||||
key string
|
||||
}
|
||||
|
||||
|
@ -78,12 +78,12 @@ func (tf *tinify) compress(url string, upload mediaStorageSaveFunc, hc *http.Cli
|
|||
ToHeaders(headers).
|
||||
Fetch(context.Background())
|
||||
if err != nil {
|
||||
log.Println("Tinify error:", err.Error())
|
||||
tf.a.error("Tinify error", "err", err)
|
||||
return "", tinifyErr
|
||||
}
|
||||
compressedLocation := headers.Get("Location")
|
||||
if compressedLocation == "" {
|
||||
log.Println("Tinify error: location header missing")
|
||||
tf.a.error("Tinify error: location header missing")
|
||||
return "", tinifyErr
|
||||
}
|
||||
// Resize and download image
|
||||
|
@ -134,9 +134,11 @@ func (*cloudflare) compress(url string, upload mediaStorageSaveFunc, hc *http.Cl
|
|||
return res, err
|
||||
}
|
||||
|
||||
type localMediaCompressor struct{}
|
||||
type localMediaCompressor struct {
|
||||
a *goBlog
|
||||
}
|
||||
|
||||
func (*localMediaCompressor) compress(url string, upload mediaStorageSaveFunc, hc *http.Client) (string, error) {
|
||||
func (lc *localMediaCompressor) compress(url string, upload mediaStorageSaveFunc, hc *http.Client) (string, error) {
|
||||
// Check url
|
||||
fileExtension, allowed := urlHasExt(url, "jpg", "jpeg", "png")
|
||||
if !allowed {
|
||||
|
@ -150,7 +152,7 @@ func (*localMediaCompressor) compress(url string, upload mediaStorageSaveFunc, h
|
|||
img, err := imaging.Decode(pr, imaging.AutoOrientation(true))
|
||||
_ = pr.CloseWithError(err)
|
||||
if err != nil {
|
||||
log.Println("Local compressor error:", err.Error())
|
||||
lc.a.error("Local compressor error", "err", err)
|
||||
return "", errors.New("failed to compress image using local compressor")
|
||||
}
|
||||
// Resize image
|
||||
|
|
|
@ -3,7 +3,6 @@ package main
|
|||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
@ -29,7 +28,7 @@ func (a *goBlog) sendNotification(text string) {
|
|||
Text: text,
|
||||
}
|
||||
if err := a.db.saveNotification(n); err != nil {
|
||||
log.Println("Failed to save notification:", err.Error())
|
||||
a.error("Failed to save notification", "err", err)
|
||||
}
|
||||
if cfg := a.cfg.Notifications; cfg != nil {
|
||||
p := pool.New().WithErrors()
|
||||
|
@ -45,7 +44,7 @@ func (a *goBlog) sendNotification(text string) {
|
|||
return err
|
||||
})
|
||||
if err := p.Wait(); err != nil {
|
||||
log.Println("Failed to send notification:", err.Error())
|
||||
a.error("Failed to send notification", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/araddon/dateparse"
|
||||
|
@ -13,21 +12,24 @@ func (a *goBlog) initPostsDeleter() {
|
|||
})
|
||||
}
|
||||
|
||||
const deletedPostParam = "deleted"
|
||||
|
||||
func (a *goBlog) checkDeletedPosts() {
|
||||
// Get all posts with `deleted` parameter and a deleted status
|
||||
postsToDelete, err := a.getPosts(&postsRequestConfig{
|
||||
status: []postStatus{statusPublishedDeleted, statusDraftDeleted, statusScheduledDeleted},
|
||||
parameter: "deleted",
|
||||
parameter: deletedPostParam,
|
||||
})
|
||||
if err != nil {
|
||||
log.Println("Error getting deleted posts:", err)
|
||||
a.error("Error getting deleted posts", "err", err)
|
||||
return
|
||||
}
|
||||
for _, post := range postsToDelete {
|
||||
// Check if post is deleted for more than 7 days
|
||||
if deleted, err := dateparse.ParseLocal(post.firstParameter("deleted")); err == nil && deleted.Add(time.Hour*24*7).Before(time.Now()) {
|
||||
if deleted, err := dateparse.ParseLocal(post.firstParameter(deletedPostParam)); err == nil &&
|
||||
deleted.Add(time.Hour*24*7).Before(time.Now()) {
|
||||
if err := a.deletePost(post.Path); err != nil {
|
||||
log.Println("Error deleting post:", err)
|
||||
a.error("Error deleting post", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -21,7 +20,7 @@ func (a *goBlog) startPostsScheduler() {
|
|||
a.shutdown.Add(func() {
|
||||
ticker.Stop()
|
||||
done <- struct{}{}
|
||||
log.Println("Posts scheduler stopped")
|
||||
a.info("Posts scheduler stopped")
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -31,16 +30,16 @@ func (a *goBlog) checkScheduledPosts() {
|
|||
publishedBefore: time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
log.Println("Error getting scheduled posts:", err)
|
||||
a.error("Error getting scheduled posts", "err", err)
|
||||
return
|
||||
}
|
||||
for _, post := range postsToPublish {
|
||||
post.Status = statusPublished
|
||||
err := a.replacePost(post, post.Path, statusScheduled, post.Visibility)
|
||||
if err != nil {
|
||||
log.Println("Error publishing scheduled post:", err)
|
||||
a.error("Error publishing scheduled post", "err", err)
|
||||
continue
|
||||
}
|
||||
log.Println("Published scheduled post:", post.Path)
|
||||
a.info("Published scheduled post", "path", post.Path)
|
||||
}
|
||||
}
|
||||
|
|
9
queue.go
9
queue.go
|
@ -4,7 +4,6 @@ import (
|
|||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -101,7 +100,7 @@ func (a *goBlog) listenOnQueue(queueName string, wait time.Duration, process que
|
|||
}
|
||||
qi, err := a.peekQueue(queueContext, queueName)
|
||||
if err != nil {
|
||||
log.Println("queue peek error:", err.Error())
|
||||
a.error("queue peek error", "err", err)
|
||||
continue queueLoop
|
||||
}
|
||||
if qi == nil {
|
||||
|
@ -117,17 +116,17 @@ func (a *goBlog) listenOnQueue(queueName string, wait time.Duration, process que
|
|||
qi,
|
||||
func() {
|
||||
if err := a.dequeue(qi); err != nil {
|
||||
log.Println("queue dequeue error:", err.Error())
|
||||
a.error("queue dequeue error", "err", err)
|
||||
}
|
||||
},
|
||||
func(dur time.Duration) {
|
||||
if err := a.reschedule(qi, dur); err != nil {
|
||||
log.Println("queue reschedule error:", err.Error())
|
||||
a.error("queue reschedule error", "err", err)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
log.Println("stopped queue:", queueName)
|
||||
a.info("stopped queue", "name", queueName)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"bytes"
|
||||
"database/sql"
|
||||
"encoding/gob"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -27,7 +26,7 @@ func (a *goBlog) initSessions() {
|
|||
"delete from sessions where expires < @now",
|
||||
sql.Named("now", utcNowString()),
|
||||
); err != nil {
|
||||
log.Println("Failed to delete expired sessions:", err.Error())
|
||||
a.error("Failed to delete expired sessions", "err", err)
|
||||
}
|
||||
}
|
||||
deleteExpiredSessions()
|
||||
|
|
85
telegram.go
85
telegram.go
|
@ -2,7 +2,6 @@ package main
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
|
@ -11,10 +10,10 @@ import (
|
|||
)
|
||||
|
||||
func (a *goBlog) initTelegram() {
|
||||
a.pPostHooks = append(a.pPostHooks, a.tgPost(false))
|
||||
a.pPostHooks = append(a.pPostHooks, func(p *post) { a.tgPost(p, false) })
|
||||
a.pUpdateHooks = append(a.pUpdateHooks, a.tgUpdate)
|
||||
a.pDeleteHooks = append(a.pDeleteHooks, a.tgDelete)
|
||||
a.pUndeleteHooks = append(a.pUndeleteHooks, a.tgPost(true))
|
||||
a.pUndeleteHooks = append(a.pUndeleteHooks, func(p *post) { a.tgPost(p, true) })
|
||||
}
|
||||
|
||||
func (tg *configTelegram) enabled() bool {
|
||||
|
@ -24,39 +23,37 @@ func (tg *configTelegram) enabled() bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (a *goBlog) tgPost(silent bool) func(*post) {
|
||||
return func(p *post) {
|
||||
if tg := a.getBlogFromPost(p).Telegram; tg.enabled() && p.isPublicPublishedSectionPost() {
|
||||
tgChat := p.firstParameter("telegramchat")
|
||||
tgMsg := p.firstParameter("telegrammsg")
|
||||
if tgChat != "" && tgMsg != "" {
|
||||
// Already posted
|
||||
return
|
||||
}
|
||||
// Generate HTML
|
||||
html := tg.generateHTML(p.RenderedTitle, a.fullPostURL(p), a.shortPostURL(p))
|
||||
if html == "" {
|
||||
return
|
||||
}
|
||||
// Send message
|
||||
chatId, msgId, err := a.sendTelegram(tg, html, tgbotapi.ModeHTML, silent)
|
||||
if err != nil {
|
||||
log.Printf("Failed to send post to Telegram: %v", err)
|
||||
return
|
||||
}
|
||||
if chatId == 0 || msgId == 0 {
|
||||
// Not sent
|
||||
return
|
||||
}
|
||||
// Save chat and message id to post
|
||||
err = a.db.replacePostParam(p.Path, "telegramchat", []string{strconv.FormatInt(chatId, 10)})
|
||||
if err != nil {
|
||||
log.Printf("Failed to save Telegram chat id: %v", err)
|
||||
}
|
||||
err = a.db.replacePostParam(p.Path, "telegrammsg", []string{strconv.Itoa(msgId)})
|
||||
if err != nil {
|
||||
log.Printf("Failed to save Telegram message id: %v", err)
|
||||
}
|
||||
func (a *goBlog) tgPost(p *post, silent bool) {
|
||||
if tg := a.getBlogFromPost(p).Telegram; tg.enabled() && p.isPublicPublishedSectionPost() {
|
||||
tgChat := p.firstParameter("telegramchat")
|
||||
tgMsg := p.firstParameter("telegrammsg")
|
||||
if tgChat != "" && tgMsg != "" {
|
||||
// Already posted
|
||||
return
|
||||
}
|
||||
// Generate HTML
|
||||
html := tg.generateHTML(p.RenderedTitle, a.fullPostURL(p), a.shortPostURL(p))
|
||||
if html == "" {
|
||||
return
|
||||
}
|
||||
// Send message
|
||||
chatId, msgId, err := a.sendTelegram(tg, html, tgbotapi.ModeHTML, silent)
|
||||
if err != nil {
|
||||
a.error("Failed to send post to Telegram", "err", err)
|
||||
return
|
||||
}
|
||||
if chatId == 0 || msgId == 0 {
|
||||
// Not sent
|
||||
return
|
||||
}
|
||||
// Save chat and message id to post
|
||||
err = a.db.replacePostParam(p.Path, "telegramchat", []string{strconv.FormatInt(chatId, 10)})
|
||||
if err != nil {
|
||||
a.error("Failed to save Telegram chat id", "err", err)
|
||||
}
|
||||
err = a.db.replacePostParam(p.Path, "telegrammsg", []string{strconv.Itoa(msgId)})
|
||||
if err != nil {
|
||||
a.error("Failed to save Telegram message id", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,13 +69,13 @@ func (a *goBlog) tgUpdate(p *post) {
|
|||
// Parse tgChat to int64
|
||||
chatId, err := strconv.ParseInt(tgChat, 10, 64)
|
||||
if err != nil {
|
||||
log.Printf("Failed to parse Telegram chat ID: %v", err)
|
||||
a.error("Failed to parse Telegram chat ID", "err", err)
|
||||
return
|
||||
}
|
||||
// Parse tgMsg to int
|
||||
messageId, err := strconv.Atoi(tgMsg)
|
||||
if err != nil {
|
||||
log.Printf("Failed to parse Telegram message ID: %v", err)
|
||||
a.error("Failed to parse Telegram message ID", "err", err)
|
||||
return
|
||||
}
|
||||
// Generate HTML
|
||||
|
@ -89,7 +86,7 @@ func (a *goBlog) tgUpdate(p *post) {
|
|||
// Send update
|
||||
err = a.updateTelegram(tg, chatId, messageId, html, "HTML")
|
||||
if err != nil {
|
||||
log.Printf("Failed to send update to Telegram: %v", err)
|
||||
a.error("Failed to send update to Telegram", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -105,28 +102,28 @@ func (a *goBlog) tgDelete(p *post) {
|
|||
// Parse tgChat to int64
|
||||
chatId, err := strconv.ParseInt(tgChat, 10, 64)
|
||||
if err != nil {
|
||||
log.Printf("Failed to parse Telegram chat ID: %v", err)
|
||||
a.error("Failed to parse Telegram chat ID", "err", err)
|
||||
return
|
||||
}
|
||||
// Parse tgMsg to int
|
||||
messageId, err := strconv.Atoi(tgMsg)
|
||||
if err != nil {
|
||||
log.Printf("Failed to parse Telegram message ID: %v", err)
|
||||
a.error("Failed to parse Telegram message ID", "err", err)
|
||||
return
|
||||
}
|
||||
// Delete message
|
||||
err = a.deleteTelegram(tg, chatId, messageId)
|
||||
if err != nil {
|
||||
log.Printf("Failed to delete Telegram message: %v", err)
|
||||
a.error("Failed to delete Telegram message", "err", err)
|
||||
}
|
||||
// Delete chat and message id from post
|
||||
err = a.db.replacePostParam(p.Path, "telegramchat", []string{})
|
||||
if err != nil {
|
||||
log.Printf("Failed to remove Telegram chat id: %v", err)
|
||||
a.error("Failed to remove Telegram chat id", "err", err)
|
||||
}
|
||||
err = a.db.replacePostParam(p.Path, "telegrammsg", []string{})
|
||||
if err != nil {
|
||||
log.Printf("Failed to remove Telegram message id: %v", err)
|
||||
a.error("Failed to remove Telegram message id", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
7
tor.go
7
tor.go
|
@ -6,7 +6,6 @@ import (
|
|||
"crypto/ed25519"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
@ -34,7 +33,7 @@ func (a *goBlog) startOnionService(h http.Handler) error {
|
|||
return err
|
||||
}
|
||||
// Start tor
|
||||
log.Println("Starting and registering onion service, please wait a couple of minutes...")
|
||||
a.info("Starting and registering onion service")
|
||||
t, err := tor.Start(context.Background(), &tor.StartConf{
|
||||
TempDataDirBase: os.TempDir(),
|
||||
NoAutoSocksPort: true,
|
||||
|
@ -64,7 +63,7 @@ func (a *goBlog) startOnionService(h http.Handler) error {
|
|||
a.torAddress = "http://" + onion.String()
|
||||
torUrl, _ := url.Parse(a.torAddress)
|
||||
a.torHostname = torUrl.Hostname()
|
||||
log.Println("Onion service published on " + a.torAddress)
|
||||
a.info("Onion service published", "address", a.torAddress)
|
||||
// Clear cache
|
||||
a.cache.purge()
|
||||
// Serve handler
|
||||
|
@ -74,7 +73,7 @@ func (a *goBlog) startOnionService(h http.Handler) error {
|
|||
ReadTimeout: 5 * time.Minute,
|
||||
WriteTimeout: 5 * time.Minute,
|
||||
}
|
||||
a.shutdown.Add(shutdownServer(s, "tor"))
|
||||
a.shutdown.Add(a.shutdownServer(s, "tor"))
|
||||
if err = s.Serve(onion); err != nil && err != http.ErrServerClosed {
|
||||
return err
|
||||
}
|
||||
|
|
11
tts.go
11
tts.go
|
@ -8,7 +8,6 @@ import (
|
|||
"fmt"
|
||||
"html"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
|
@ -38,7 +37,7 @@ func (a *goBlog) initTTS() {
|
|||
// Create TTS audio
|
||||
err := a.createPostTTSAudio(p)
|
||||
if err != nil {
|
||||
log.Printf("create post audio for %s failed: %v", p.Path, err)
|
||||
a.error("create post audio failed", "path", p.Path, "err", err)
|
||||
}
|
||||
}
|
||||
a.pPostHooks = append(a.pPostHooks, createOrUpdate)
|
||||
|
@ -47,7 +46,7 @@ func (a *goBlog) initTTS() {
|
|||
a.pDeleteHooks = append(a.pDeleteHooks, func(p *post) {
|
||||
// Try to delete the audio file
|
||||
if a.deletePostTTSAudio(p) {
|
||||
log.Println("deleted tts audio for", p.Path)
|
||||
a.info("deleted tts audio", "path", p.Path)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -131,7 +130,7 @@ func (a *goBlog) createPostTTSAudio(p *post) error {
|
|||
// Already has tts audio, but with different location
|
||||
// Try to delete the old audio file
|
||||
if a.deletePostTTSAudio(p) {
|
||||
log.Println("deleted old tts audio for", p.Path)
|
||||
a.info("deleted old tts audio", "path", p.Path)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,7 +157,7 @@ func (a *goBlog) deletePostTTSAudio(p *post) bool {
|
|||
fileUrl, err := url.Parse(audio)
|
||||
if err != nil {
|
||||
// Failed to parse audio url
|
||||
log.Println("failed to parse audio url:", err)
|
||||
a.error("failed to parse audio url", "err", err, "audio", audio)
|
||||
return false
|
||||
}
|
||||
fileName := path.Base(fileUrl.Path)
|
||||
|
@ -169,7 +168,7 @@ func (a *goBlog) deletePostTTSAudio(p *post) bool {
|
|||
// Try to delete the audio file
|
||||
err = a.deleteMediaFile(fileName)
|
||||
if err != nil {
|
||||
log.Println("failed to delete audio file:", err)
|
||||
a.error("failed to delete audio file", "err", err, "file", fileName)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
|
|
@ -52,35 +52,35 @@ func (a *goBlog) initWebmention() {
|
|||
func (a *goBlog) handleWebmention(w http.ResponseWriter, r *http.Request) {
|
||||
m, err := a.extractMention(r)
|
||||
if err != nil {
|
||||
a.debug("Error extracting webmention:", err.Error())
|
||||
a.debug("Error extracting webmention", "err", err)
|
||||
a.serveError(w, r, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
hasShortPrefix := a.cfg.Server.ShortPublicAddress != "" && strings.HasPrefix(m.Target, a.cfg.Server.ShortPublicAddress)
|
||||
hasLongPrefix := strings.HasPrefix(m.Target, a.cfg.Server.PublicAddress)
|
||||
if !hasShortPrefix && !hasLongPrefix {
|
||||
a.debug("Webmention target not allowed:", m.Target)
|
||||
a.debug("Webmention target not allowed", "target", m.Target)
|
||||
a.serveError(w, r, "target not allowed", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if m.Target == m.Source {
|
||||
a.debug("Webmention target and source are the same:", m.Target)
|
||||
a.debug("Webmention target and source are the same", "target", m.Target)
|
||||
a.serveError(w, r, "target and source are the same", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err = a.queueMention(m); err != nil {
|
||||
a.debug("Failed to queue webmention", err.Error())
|
||||
a.debug("Failed to queue webmention", "err", err)
|
||||
a.serveError(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
_, _ = fmt.Fprint(w, "Webmention accepted")
|
||||
a.debug("Accepted webmention:", m.Source, m.Target)
|
||||
a.debug("Accepted webmention", "source", m.Source, "target", m.Target)
|
||||
}
|
||||
|
||||
func (a *goBlog) extractMention(r *http.Request) (*mention, error) {
|
||||
if ct := r.Header.Get(contentType); !strings.Contains(ct, contenttype.WWWForm) {
|
||||
a.debug("New webmention request with wrong content type:", ct)
|
||||
a.debug("New webmention request with wrong content type", "ct", ct)
|
||||
return nil, errors.New("unsupported Content-Type")
|
||||
}
|
||||
err := r.ParseForm()
|
||||
|
@ -90,7 +90,7 @@ func (a *goBlog) extractMention(r *http.Request) (*mention, error) {
|
|||
source := r.Form.Get("source")
|
||||
target := r.Form.Get("target")
|
||||
if source == "" || target == "" || !isAbsoluteURL(source) || !isAbsoluteURL(target) {
|
||||
a.debug("Invalid webmention request, source:", source, "target:", target)
|
||||
a.debug("Invalid webmention request", "source", source, "target", target)
|
||||
return nil, errors.New("invalid request")
|
||||
}
|
||||
return &mention{
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
@ -49,7 +48,7 @@ func (a *goBlog) sendWebmentions(p *post) error {
|
|||
if strings.HasPrefix(link, a.cfg.Server.PublicAddress) {
|
||||
// Save mention directly
|
||||
if err := a.createWebmention(a.fullPostURL(p), link); err != nil {
|
||||
log.Println("Failed to create webmention:", err.Error())
|
||||
a.error("Failed to create webmention", "err", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
@ -64,10 +63,10 @@ func (a *goBlog) sendWebmentions(p *post) error {
|
|||
continue
|
||||
}
|
||||
if err = a.sendWebmention(endpoint, a.fullPostURL(p), link); err != nil {
|
||||
log.Println("Sending webmention to " + link + " failed")
|
||||
a.error("Sending webmention failed", "link", link)
|
||||
continue
|
||||
}
|
||||
log.Println("Sent webmention to " + link)
|
||||
a.info("Sent webmention", "link", link)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -21,12 +20,12 @@ func (a *goBlog) initWebmentionQueue() {
|
|||
a.listenOnQueue("wm", 30*time.Second, func(qi *queueItem, dequeue func(), reschedule func(time.Duration)) {
|
||||
var m mention
|
||||
if err := gob.NewDecoder(bytes.NewReader(qi.content)).Decode(&m); err != nil {
|
||||
log.Println("webmention queue:", err.Error())
|
||||
a.error("webmention queue error", "err", err)
|
||||
dequeue()
|
||||
return
|
||||
}
|
||||
if err := a.verifyMention(&m); err != nil {
|
||||
log.Printf("Failed to verify webmention from %s to %s: %s", m.Source, m.Target, err.Error())
|
||||
a.error("Failed to verify webmention", "source", m.Source, "target", m.Target, "err", err)
|
||||
}
|
||||
dequeue()
|
||||
})
|
||||
|
@ -59,9 +58,7 @@ func (a *goBlog) verifyMention(m *mention) error {
|
|||
_ = targetResp.Body.Close()
|
||||
// Check if target has a valid status code
|
||||
if targetResp.StatusCode != http.StatusOK {
|
||||
if a.cfg.Debug {
|
||||
a.debug(fmt.Sprintf("Webmention for unknown path: %s", m.Target))
|
||||
}
|
||||
a.debug("Webmention for unknown path", "target", m.Target)
|
||||
return a.db.deleteWebmention(m)
|
||||
}
|
||||
// Check if target has a redirect
|
||||
|
@ -94,9 +91,7 @@ func (a *goBlog) verifyMention(m *mention) error {
|
|||
}
|
||||
// Check if source has a valid status code
|
||||
if sourceResp.StatusCode != http.StatusOK {
|
||||
if a.cfg.Debug {
|
||||
a.debug(fmt.Sprintf("Delete webmention because source doesn't have valid status code: %s", m.Source))
|
||||
}
|
||||
a.debug("Delete webmention because source doesn't have valid status code", "source", m.Source)
|
||||
return a.db.deleteWebmention(m)
|
||||
}
|
||||
// Check if source has a redirect
|
||||
|
@ -108,17 +103,13 @@ func (a *goBlog) verifyMention(m *mention) error {
|
|||
// Parse response body
|
||||
err = a.verifyReader(m, sourceResp.Body)
|
||||
if err != nil {
|
||||
if a.cfg.Debug {
|
||||
a.debug(fmt.Sprintf("Delete webmention because verifying %s threw error: %s", m.Source, err.Error()))
|
||||
}
|
||||
a.debug("Delete webmention because verifying source threw error", "source", m.Source, "err", err)
|
||||
return a.db.deleteWebmention(m)
|
||||
}
|
||||
newStatus := webmentionStatusVerified
|
||||
// Update or insert webmention
|
||||
if a.db.webmentionExists(m) {
|
||||
if a.cfg.Debug {
|
||||
a.debug(fmt.Sprintf("Update webmention: %s => %s", m.Source, m.Target))
|
||||
}
|
||||
a.debug("Update webmention", "source", m.Source, "target", m.Target)
|
||||
// Update webmention
|
||||
err = a.db.updateWebmention(m, newStatus)
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in New Issue