Skip to content

Commit 667180e

Browse files
authored
syslog: local syslog and stdout redirection (#344)
1 parent e5b17d3 commit 667180e

File tree

10 files changed

+125
-29
lines changed

10 files changed

+125
-29
lines changed

config/global.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ type Global struct {
2626
MinMemory uint64 `mapstructure:"min-memory" default:"100" description:"Minimum available memory (in MB) required to run any commands - see https://creativeprojects.github.io/resticprofile/usage/memory/"`
2727
Scheduler string `mapstructure:"scheduler" description:"Leave blank for the default scheduler or use \"crond\" to select cron on supported operating systems"`
2828
ScheduleDefaults *ScheduleBaseConfig `mapstructure:"schedule-defaults" default:"" description:"Sets defaults for all schedules"`
29-
Log string `mapstructure:"log" default:"" description:"Sets the default log destination to be used if not specified in '--log' or 'schedule-log' - see https://creativeprojects.github.io/resticprofile/configuration/logs/"`
29+
Log string `mapstructure:"log" default:"" examples:"/resticprofile.log;syslog-tcp://syslog-server:514;syslog:server;syslog:" description:"Sets the default log destination to be used if not specified in '--log' or 'schedule-log' - see https://creativeprojects.github.io/resticprofile/configuration/logs/"`
3030
LegacyArguments bool `mapstructure:"legacy-arguments" default:"false" deprecated:"0.20.0" description:"Legacy, broken arguments mode of resticprofile before version 0.15"`
3131
SystemdUnitTemplate string `mapstructure:"systemd-unit-template" default:"" description:"File containing the go template to generate a systemd unit - see https://creativeprojects.github.io/resticprofile/schedules/systemd/"`
3232
SystemdTimerTemplate string `mapstructure:"systemd-timer-template" default:"" description:"File containing the go template to generate a systemd timer - see https://creativeprojects.github.io/resticprofile/schedules/systemd/"`

config/profile.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ type ScheduleBaseSection struct {
298298
scheduleConfig *ScheduleConfig
299299
Schedule any `mapstructure:"schedule" show:"noshow" examples:"hourly;daily;weekly;monthly;10:00,14:00,18:00,22:00;Wed,Fri 17:48;*-*-15 02:45;Mon..Fri 00:30" description:"Configures the scheduled execution of this profile section. Can be times in systemd timer format or a config structure"`
300300
SchedulePermission string `mapstructure:"schedule-permission" show:"noshow" default:"auto" enum:"auto;system;user;user_logged_on" description:"Specify whether the schedule runs with system or user privileges - see https://creativeprojects.github.io/resticprofile/schedules/configuration/"`
301-
ScheduleLog string `mapstructure:"schedule-log" show:"noshow" examples:"/resticprofile.log;tcp://localhost:514" description:"Redirect the output into a log file or to syslog when running on schedule"`
301+
ScheduleLog string `mapstructure:"schedule-log" show:"noshow" examples:"/resticprofile.log;syslog-tcp://syslog-server:514;syslog:server;syslog:" description:"Redirect the output into a log file or to syslog when running on schedule"`
302302
SchedulePriority string `mapstructure:"schedule-priority" show:"noshow" default:"background" enum:"background;standard" description:"Set the priority at which the schedule is run"`
303303
ScheduleLockMode string `mapstructure:"schedule-lock-mode" show:"noshow" default:"default" enum:"default;fail;ignore" description:"Specify how locks are used when running on schedule - see https://creativeprojects.github.io/resticprofile/schedules/configuration/"`
304304
ScheduleLockWait maybe.Duration `mapstructure:"schedule-lock-wait" show:"noshow" examples:"150s;15m;30m;45m;1h;2h30m" description:"Set the maximum time to wait for acquiring locks when running on schedule"`

config/schedule.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const (
3535
// ScheduleBaseConfig is the base user configuration that could be shared across all schedules.
3636
type ScheduleBaseConfig struct {
3737
Permission string `mapstructure:"permission" default:"auto" enum:"auto;system;user;user_logged_on" description:"Specify whether the schedule runs with system or user privileges - see https://creativeprojects.github.io/resticprofile/schedules/configuration/"`
38-
Log string `mapstructure:"log" examples:"/resticprofile.log;tcp://localhost:514" description:"Redirect the output into a log file or to syslog when running on schedule"`
38+
Log string `mapstructure:"log" examples:"/resticprofile.log;syslog-tcp://syslog-server:514;syslog:server;syslog:" description:"Redirect the output into a log file or to syslog when running on schedule"`
3939
Priority string `mapstructure:"priority" default:"background" enum:"background;standard" description:"Set the priority at which the schedule is run"`
4040
LockMode string `mapstructure:"lock-mode" default:"default" enum:"default;fail;ignore" description:"Specify how locks are used when running on schedule - see https://creativeprojects.github.io/resticprofile/schedules/configuration/"`
4141
LockWait maybe.Duration `mapstructure:"lock-wait" examples:"150s;15m;30m;45m;1h;2h30m" description:"Set the maximum time to wait for acquiring locks when running on schedule"`

dial/url.go

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,40 @@
11
package dial
22

3-
import "net/url"
3+
import (
4+
"net/url"
5+
"slices"
6+
"strings"
7+
8+
"github.com/creativeprojects/clog"
9+
)
10+
11+
var validSchemes = []string{
12+
"udp",
13+
"tcp",
14+
"syslog", // local or UDP
15+
"syslog-tcp", // TCP
16+
// "syslog-tls", reserved for future support
17+
}
18+
19+
var noHostAllowed = []string{
20+
"syslog",
21+
}
422

523
// GetAddr returns scheme, host&port, isURL
624
func GetAddr(source string) (scheme, hostPort string, isURL bool) {
725
URL, err := url.Parse(source)
8-
if err != nil {
9-
return "", "", false
10-
}
11-
// need a minimum of udp://:12
12-
if len(URL.Scheme) < 3 || len(URL.Host) < 3 {
13-
return "", "", false
26+
if err == nil {
27+
scheme = strings.ToLower(URL.Scheme)
28+
hostPort = URL.Host
29+
schemeOk := slices.Contains(validSchemes, scheme)
30+
hostOk := len(hostPort) >= 3 || slices.Contains(noHostAllowed, scheme)
31+
if isURL = schemeOk && hostOk; isURL {
32+
return
33+
}
34+
} else {
35+
clog.Tracef("is not an URL %q", source)
1436
}
15-
return URL.Scheme, URL.Host, true
37+
return "", "", false
1638
}
1739

1840
func IsURL(source string) bool {

dial/url_test.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,24 @@ func TestGetDialAddr(t *testing.T) {
1616
}{
1717
// invalid
1818
{"://", "", "", false},
19+
// supported schemes
20+
{"TCP://:123", "tcp", ":123", true},
21+
{"UDP://:123", "udp", ":123", true},
22+
{"tcp://:123", "tcp", ":123", true},
23+
{"udp://:123", "udp", ":123", true},
24+
{"syslog://:123", "syslog", ":123", true},
25+
{"syslog-tcp://:123", "syslog-tcp", ":123", true},
1926
// url
20-
{"scheme://:123", "scheme", ":123", true},
21-
{"scheme://host:123", "scheme", "host:123", true},
22-
{"scheme://host", "scheme", "host", true},
27+
{"syslog://:123", "syslog", ":123", true},
28+
{"syslog://host:123", "syslog", "host:123", true},
29+
{"syslog://host", "syslog", "host", true},
30+
{"syslog://", "syslog", "", true},
31+
{"syslog:", "syslog", "", true},
2332
// too short
24-
{"scheme://", "", "", false},
25-
{"scheme://:", "", "", false},
33+
{"tcp://", "", "", false},
34+
{"tcp:", "", "", false},
35+
{"syslog-tcp:", "", "", false},
36+
{"udp:", "", "", false},
2637
{"c://", "", "", false},
2738
{"c://:", "", "", false},
2839
{"c://:123", "", "", false},

docs/content/configuration/logs.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ The log destination syntax is a such:
1414
* `-` {{% icon icon="arrow-right" %}} redirects all the logs to the console / stdout (is the default log destination)
1515
* `filename` {{% icon icon="arrow-right" %}} redirects all the logs to the local file called **filename**
1616
* `temp:filename` {{% icon icon="arrow-right" %}} redirects all the logs to a temporary file available during the whole session, and deleted afterwards.
17-
* `tcp://syslog_server:514` or `udp://syslog_server:514` {{% icon icon="arrow-right" %}} redirects all the logs to the **syslog** server.
17+
* `syslog:`, `syslog://syslog_server[:514]` or `syslog-tcp://syslog_server[:514]` {{% icon icon="arrow-right" %}} redirects all the logs to a local or remote **syslog** server. Alternative configurations for remote servers are: `udp://syslog_server:514` & `tcp://syslog_server:514`.
1818

1919
{{% notice style="note" %}}
2020
Logging to syslog is not available on Windows.
@@ -36,6 +36,8 @@ version = "1"
3636

3737
[global]
3838
log = "resticprofile.log"
39+
[global.schedule-defaults]
40+
log = "scheduled-resticprofile.log"
3941
```
4042

4143
{{% /tab %}}
@@ -46,6 +48,8 @@ version: "1"
4648

4749
global:
4850
log: "resticprofile.log"
51+
schedule-defaults:
52+
log: "scheduled-resticprofile.log"
4953
```
5054
5155
{{% /tab %}}
@@ -54,6 +58,9 @@ global:
5458
```hcl
5559
"global" {
5660
"log" = "resticprofile.log"
61+
"schedule-defaults" {
62+
"log" = "scheduled-resticprofile.log"
63+
}
5764
}
5865
```
5966

@@ -64,7 +71,10 @@ global:
6471
{
6572
"version": "1",
6673
"global": {
67-
"log": "resticprofile.log"
74+
"log": "resticprofile.log",
75+
"schedule-defaults": {
76+
"log": "scheduled-resticprofile.log"
77+
}
6878
}
6979
}
7080
```

logger.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func setupTargetLogger(flags commandLineFlags, logTarget string) (io.Closer, err
4848
)
4949
scheme, hostPort, isURL := dial.GetAddr(logTarget)
5050
if isURL {
51-
handler, err = getSyslogHandler(scheme, hostPort)
51+
handler, file, err = getSyslogHandler(scheme, hostPort)
5252
} else {
5353
handler, file, err = getFileHandler(logTarget)
5454
}

syslog.go

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@
33
package main
44

55
import (
6+
"bytes"
67
"errors"
78
"fmt"
9+
"io"
810
"log/syslog"
11+
"net"
12+
"strings"
913

1014
"github.com/creativeprojects/clog"
1115
"github.com/creativeprojects/resticprofile/constants"
@@ -48,11 +52,58 @@ func (l *Syslog) Close() error {
4852

4953
var _ LogCloser = &Syslog{}
5054

51-
func getSyslogHandler(scheme, hostPort string) (*Syslog, error) {
52-
writer, err := syslog.Dial(scheme, hostPort, syslog.LOG_USER|syslog.LOG_NOTICE, constants.ApplicationName)
53-
if err != nil {
54-
return nil, fmt.Errorf("cannot open syslog logger: %w", err)
55+
const DefaultSyslogPort = "514"
56+
57+
type tokenWriter struct {
58+
separator []byte
59+
target io.Writer
60+
}
61+
62+
func (s *tokenWriter) Write(p []byte) (n int, err error) {
63+
var pn int
64+
for i, part := range bytes.Split(p, s.separator) {
65+
if err != nil {
66+
break
67+
}
68+
pn, err = s.target.Write(part)
69+
n += pn
70+
if i > 0 {
71+
n += len(s.separator)
72+
}
73+
}
74+
return
75+
}
76+
77+
func getSyslogHandler(scheme, hostPort string) (handler *Syslog, writer io.Writer, err error) {
78+
switch scheme {
79+
case "udp", "tcp":
80+
case "syslog-tcp":
81+
scheme = "tcp"
82+
case "syslog":
83+
if len(hostPort) == 0 {
84+
scheme = "local"
85+
} else {
86+
scheme = "udp"
87+
}
88+
default:
89+
err = fmt.Errorf("unsupported scheme %q", scheme)
90+
}
91+
92+
var logger *syslog.Writer
93+
if scheme == "local" {
94+
logger, err = syslog.New(syslog.LOG_USER|syslog.LOG_NOTICE, constants.ApplicationName)
95+
} else {
96+
if _, _, e := net.SplitHostPort(hostPort); e != nil && strings.Contains(e.Error(), "missing port") {
97+
hostPort = net.JoinHostPort(hostPort, DefaultSyslogPort)
98+
}
99+
logger, err = syslog.Dial(scheme, hostPort, syslog.LOG_USER|syslog.LOG_NOTICE, constants.ApplicationName)
100+
}
101+
102+
if err == nil {
103+
writer = &tokenWriter{separator: []byte("\n"), target: logger}
104+
handler = NewSyslogHandler(logger)
105+
} else {
106+
err = fmt.Errorf("cannot open syslog logger: %w", err)
55107
}
56-
handler := NewSyslogHandler(writer)
57-
return handler, nil
108+
return
58109
}

syslog_windows.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ package main
44

55
import (
66
"errors"
7+
"io"
78
)
89

9-
func getSyslogHandler(scheme, hostPort string) (LogCloser, error) {
10-
return nil, errors.New("syslog is not supported on Windows")
10+
func getSyslogHandler(scheme, hostPort string) (_ LogCloser, _ io.Writer, err error) {
11+
err = errors.New("syslog is not supported on Windows")
12+
return
1113
}

wrapper.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -632,8 +632,8 @@ func (r *resticWrapper) runFinalShellCommands(command string, fail error) {
632632
term.FlushAllOutput()
633633
_, _, err := runShellCommand(rCommand)
634634
if err != nil {
635-
clog.Errorf("run-finally command %d/%d failed ('%s' on profile '%s'): %w",
636-
index+1, len(commands), command, r.profile.Name, err)
635+
clog.Errorf("run-finally command %d/%d failed ('%s' on profile '%s'): %s",
636+
index+1, len(commands), command, r.profile.Name, err.Error())
637637
}
638638
}(i, commands[i])
639639
}

0 commit comments

Comments
 (0)