Skip to content

Commit 08cfa64

Browse files
authored
Merge pull request #1052 from percona/PBM-1226-logpath-opt-no-cfg
PBM-1226: `logpath` option for pbm-agent
2 parents e7ce3dd + e6ebd85 commit 08cfa64

File tree

5 files changed

+225
-32
lines changed

5 files changed

+225
-32
lines changed

cmd/pbm-agent/main.go

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,20 @@ func main() {
4949
versionFormat = versionCmd.Flag("format", "Output format <json or \"\">").
5050
Default("").
5151
String()
52+
53+
logPath = pbmCmd.Flag("log-path", "Path to file").
54+
Envar("LOG_PATH").
55+
Default("/dev/stderr").
56+
String()
57+
logJSON = pbmCmd.Flag("log-json", "Enable JSON output").
58+
Envar("LOG_JSON").
59+
Bool()
60+
logLevel = pbmCmd.Flag(
61+
"log-level",
62+
"Minimal log level based on severity level: D, I, W, E or F, low to high. Choosing one includes higher levels too.").
63+
Envar("LOG_LEVEL").
64+
Default(log.D).
65+
Enum(log.D, log.I, log.W, log.E, log.F)
5266
)
5367

5468
cmd, err := pbmCmd.DefaultEnvars().Parse(os.Args[1:])
@@ -74,19 +88,24 @@ func main() {
7488

7589
hidecreds()
7690

77-
fmt.Print(perconaSquadNotice)
91+
logOpts := &log.Opts{
92+
LogPath: *logPath,
93+
LogLevel: *logLevel,
94+
LogJSON: *logJSON,
95+
}
7896

79-
err = runAgent(url, *dumpConns)
97+
err = runAgent(url, *dumpConns, logOpts)
8098
stdlog.Println("Exit:", err)
8199
if err != nil {
82100
os.Exit(1)
83101
}
84102
}
85103

86-
func runAgent(mongoURI string, dumpConns int) error {
87-
mtLog.SetDateFormat(log.LogTimeFormat)
88-
mtLog.SetVerbosity(&options.Verbosity{VLevel: mtLog.DebugLow})
89-
104+
func runAgent(
105+
mongoURI string,
106+
dumpConns int,
107+
logOpts *log.Opts,
108+
) error {
90109
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
91110
defer cancel()
92111

@@ -95,16 +114,34 @@ func runAgent(mongoURI string, dumpConns int) error {
95114
return errors.Wrap(err, "connect to PBM")
96115
}
97116

117+
err = setupNewDB(ctx, leadConn)
118+
if err != nil {
119+
return errors.Wrap(err, "setup pbm collections")
120+
}
121+
98122
agent, err := newAgent(ctx, leadConn, mongoURI, dumpConns)
99123
if err != nil {
100124
return errors.Wrap(err, "connect to the node")
101125
}
102126

103-
logger := log.New(agent.leadConn.LogCollection(), agent.brief.SetName, agent.brief.Me)
127+
logger := log.NewWithOpts(
128+
ctx,
129+
agent.leadConn,
130+
agent.brief.SetName,
131+
agent.brief.Me,
132+
logOpts)
104133
defer logger.Close()
105134

106135
ctx = log.SetLoggerToContext(ctx, logger)
107136

137+
mtLog.SetDateFormat(log.LogTimeFormat)
138+
mtLog.SetVerbosity(&options.Verbosity{VLevel: mtLog.DebugLow})
139+
mtLog.SetWriter(logger)
140+
141+
logger.Printf(perconaSquadNotice)
142+
logger.Printf("log options: log-path=%s, log-level:%s, log-json:%t",
143+
logOpts.LogPath, logOpts.LogLevel, logOpts.LogJSON)
144+
108145
canRunSlicer := true
109146
if err := agent.CanStart(ctx); err != nil {
110147
if errors.Is(err, ErrArbiterNode) || errors.Is(err, ErrDelayedNode) {
@@ -114,11 +151,6 @@ func runAgent(mongoURI string, dumpConns int) error {
114151
}
115152
}
116153

117-
err = setupNewDB(ctx, agent.leadConn)
118-
if err != nil {
119-
return errors.Wrap(err, "setup pbm collections")
120-
}
121-
122154
agent.showIncompatibilityWarning(ctx)
123155

124156
if canRunSlicer {

cmd/pbm/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,7 @@ func main() {
495495
if err != nil {
496496
exitErr(errors.Wrap(err, "connect to mongodb"), pbmOutF)
497497
}
498-
ctx = log.SetLoggerToContext(ctx, log.New(conn.LogCollection(), "", ""))
498+
ctx = log.SetLoggerToContext(ctx, log.New(conn, "", ""))
499499

500500
ver, err := version.GetMongoVersion(ctx, conn.MongoClient())
501501
if err != nil {

pbm/log/discard.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ func (discardLoggerImpl) PauseMgo() {
2828
func (discardLoggerImpl) ResumeMgo() {
2929
}
3030

31+
func (discardLoggerImpl) Write([]byte) (int, error) {
32+
return 0, nil
33+
}
34+
3135
func (discardLoggerImpl) Printf(msg string, args ...any) {
3236
}
3337

pbm/log/log.go

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ type Logger interface {
2626
PauseMgo()
2727
ResumeMgo()
2828

29+
Write(p []byte) (n int, err error)
30+
2931
Printf(msg string, args ...any)
3032
Debug(event, obj, opid string, epoch primitive.Timestamp, msg string, args ...any)
3133
Info(event, obj, opid string, epoch primitive.Timestamp, msg string, args ...any)
@@ -60,19 +62,44 @@ const (
6062
Debug
6163
)
6264

65+
const (
66+
F = "F"
67+
E = "E"
68+
W = "W"
69+
I = "I"
70+
D = "D"
71+
)
72+
6373
func (s Severity) String() string {
6474
switch s {
6575
case Fatal:
66-
return "F"
76+
return F
6777
case Error:
68-
return "E"
78+
return E
6979
case Warning:
70-
return "W"
80+
return W
7181
case Info:
72-
return "I"
82+
return I
7383
case Debug:
74-
return "D"
84+
return D
7585
default:
7686
return ""
7787
}
7888
}
89+
90+
func strToSeverity(s string) Severity {
91+
switch s {
92+
case F:
93+
return Fatal
94+
case E:
95+
return Error
96+
case W:
97+
return Warning
98+
case I:
99+
return Info
100+
case D:
101+
return Debug
102+
default:
103+
return Debug
104+
}
105+
}

pbm/log/logger.go

Lines changed: 144 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,149 @@ import (
55
"encoding/json"
66
"fmt"
77
"io"
8+
"io/fs"
89
"log"
910
"os"
11+
"path/filepath"
12+
"strings"
13+
"sync"
1014
"sync/atomic"
1115
"time"
1216

1317
"go.mongodb.org/mongo-driver/bson/primitive"
14-
"go.mongodb.org/mongo-driver/mongo"
1518

19+
"github.com/percona/percona-backup-mongodb/pbm/connect"
1620
"github.com/percona/percona-backup-mongodb/pbm/errors"
1721
)
1822

23+
const (
24+
logPathStdErr = "/dev/stderr"
25+
)
26+
1927
type loggerImpl struct {
20-
cn *mongo.Collection
21-
out io.Writer
28+
conn connect.Client
29+
30+
mu sync.Mutex
31+
logger *logger
32+
2233
rs string
2334
node string
2435

2536
buf Buffer
2637
bufSet atomic.Uint32
2738

2839
pauseMgo int32
40+
41+
logLevel Severity
42+
logJSON bool
2943
}
3044

31-
func New(cn *mongo.Collection, rs, node string) Logger {
45+
// New creates default logger which outputs to stderr.
46+
func New(conn connect.Client, rs, node string) Logger {
3247
return &loggerImpl{
33-
cn: cn,
34-
out: os.Stderr,
35-
rs: rs,
36-
node: node,
48+
conn: conn,
49+
logger: newStdLogger(),
50+
rs: rs,
51+
node: node,
52+
logLevel: Debug,
53+
}
54+
}
55+
56+
// NewWithOpts creates logger based on provided options.
57+
func NewWithOpts(ctx context.Context, conn connect.Client, rs, node string, opts *Opts) Logger {
58+
l := &loggerImpl{
59+
conn: conn,
60+
rs: rs,
61+
node: node,
62+
logLevel: strToSeverity(opts.LogLevel),
63+
logJSON: opts.LogJSON,
64+
}
65+
66+
l.mu.Lock()
67+
l.createLogger(opts.LogPath)
68+
l.mu.Unlock()
69+
70+
return l
71+
}
72+
73+
// Opts represents options that are specified by the user.
74+
type Opts struct {
75+
LogPath string
76+
LogJSON bool
77+
LogLevel string
78+
}
79+
80+
type logger struct {
81+
out io.Writer
82+
logPath string
83+
}
84+
85+
func newStdLogger() *logger {
86+
return &logger{
87+
out: os.Stderr,
88+
logPath: logPathStdErr,
89+
}
90+
}
91+
92+
func newFileLogger(logPath string) (*logger, error) {
93+
fullpath, err := filepath.Abs(logPath)
94+
if err != nil {
95+
return nil, errors.Wrap(err, "abs")
96+
}
97+
98+
fileInfo, err := os.Stat(fullpath)
99+
if err != nil {
100+
if !os.IsNotExist(err) {
101+
return nil, errors.Wrap(err, "stat")
102+
}
103+
104+
dirpath := filepath.Dir(fullpath)
105+
err = os.MkdirAll(dirpath, fs.ModeDir|0o777)
106+
if err != nil {
107+
return nil, errors.Wrap(err, "mkdir -p")
108+
}
109+
} else if fileInfo.IsDir() {
110+
return nil, errors.New("path is dir")
111+
}
112+
113+
out, err := os.OpenFile(fullpath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
114+
if err != nil {
115+
return nil, errors.Wrap(err, "open file")
116+
}
117+
118+
return &logger{
119+
out: out,
120+
logPath: logPath,
121+
}, nil
122+
}
123+
124+
func (l *logger) close() {
125+
if l == nil || l.out == nil ||
126+
l.logPath == "" || l.logPath == logPathStdErr {
127+
return
128+
}
129+
if o, ok := l.out.(io.Closer); ok {
130+
o.Close()
131+
}
132+
}
133+
134+
// createLogger creates file/stderr type of logger based on logPath.
135+
// In case of an error during file logger creation, it falls back to stderr logger.
136+
func (l *loggerImpl) createLogger(logPath string) {
137+
// close old one first
138+
l.logger.close()
139+
140+
// and create new one
141+
if strings.TrimSpace(logPath) == "" || logPath == logPathStdErr {
142+
l.logger = newStdLogger()
143+
} else {
144+
fl, err := newFileLogger(logPath)
145+
if err != nil {
146+
l.logger = newStdLogger()
147+
log.Printf("[ERROR] error while creating file logger: %v", err)
148+
} else {
149+
l.logger = fl
150+
}
37151
}
38152
}
39153

@@ -77,6 +191,13 @@ func (l *loggerImpl) ResumeMgo() {
77191
atomic.StoreInt32(&l.pauseMgo, 0)
78192
}
79193

194+
func (l *loggerImpl) Write(p []byte) (int, error) {
195+
l.mu.Lock()
196+
defer l.mu.Unlock()
197+
198+
return l.logger.out.Write(p)
199+
}
200+
80201
func (l *loggerImpl) output(
81202
s Severity,
82203
event,
@@ -111,7 +232,7 @@ func (l *loggerImpl) output(
111232

112233
err := l.Output(context.TODO(), e)
113234
if err != nil {
114-
log.Printf("[ERROR] wrting log: %v, entry: %s", err, e)
235+
log.Printf("[ERROR] writing log: %v, entry: %s", err, e)
115236
}
116237
}
117238

@@ -142,8 +263,9 @@ func (l *loggerImpl) Fatal(event, obj, opid string, epoch primitive.Timestamp, m
142263
func (l *loggerImpl) Output(ctx context.Context, e *Entry) error {
143264
var rerr error
144265

145-
if l.cn != nil && atomic.LoadInt32(&l.pauseMgo) == 0 {
146-
_, err := l.cn.InsertOne(ctx, e)
266+
// conn is nil during physical restore
267+
if l.conn != nil && l.conn.LogCollection() != nil && atomic.LoadInt32(&l.pauseMgo) == 0 {
268+
_, err := l.conn.LogCollection().InsertOne(ctx, e)
147269
if err != nil {
148270
rerr = errors.Wrap(err, "db")
149271
}
@@ -162,10 +284,18 @@ func (l *loggerImpl) Output(ctx context.Context, e *Entry) error {
162284
}
163285
}
164286

165-
if l.out != nil {
166-
_, err := l.out.Write(append([]byte(e.String()), '\n'))
287+
l.mu.Lock()
288+
defer l.mu.Unlock()
289+
if l.logger != nil && l.logLevel >= e.Severity {
290+
var err error
291+
if l.logJSON {
292+
err = json.NewEncoder(l.logger.out).Encode(e)
293+
err = errors.Wrap(err, "io json")
294+
} else {
295+
_, err = l.logger.out.Write(append([]byte(e.String()), '\n'))
296+
err = errors.Wrap(err, "io text")
297+
}
167298

168-
err = errors.Wrap(err, "io")
169299
if rerr != nil {
170300
rerr = errors.Errorf("%v, %v", rerr, err)
171301
} else {

0 commit comments

Comments
 (0)