-
Notifications
You must be signed in to change notification settings - Fork 3.9k
/
Copy pathflags.go
164 lines (143 loc) · 6.18 KB
/
flags.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
// Copyright 2015 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
package log
import (
"context"
"flag"
"github.com/cockroachdb/cockroach/pkg/util/log/logflags"
"github.com/cockroachdb/errors"
)
func init() {
logflags.InitFlags(
&mainLog.logDir,
&showLogs,
&logging.noColor,
&redactableLogsRequested, // NB: see doc on the variable definition.
&logging.vmoduleConfig.mu.vmodule,
&LogFileMaxSize, &LogFilesCombinedMaxSize,
)
// We define these flags here because they have the type Severity
// which we can't pass to logflags without creating an import cycle.
flag.Var(&mainLog.stderrThreshold,
logflags.LogToStderrName, "logs at or above this threshold go to stderr")
flag.Var(&mainLog.fileThreshold,
logflags.LogFileVerbosityThresholdName, "minimum verbosity of messages written to the log file")
}
// IsActive returns true iff the main logger already has some events
// logged, or some secondary logger was created with configuration
// taken from the main logger.
//
// This is used to assert that configuration is performed
// before logging has been used for the first time.
func IsActive() (active bool, firstUse string) {
logging.mu.Lock()
defer logging.mu.Unlock()
return logging.mu.active, logging.mu.firstUseStack
}
// SetupRedactionAndStderrRedirects should be called once after
// command-line flags have been parsed, and before the first log entry
// is emitted.
//
// The returned cleanup fn can be invoked by the caller to terminate
// the secondary logger. This is only useful in tests: for a
// long-running server process the cleanup function should likely not
// be called, to ensure that the file used to capture direct stderr
// writes remains open up until the process entirely terminates. This
// ensures that any Go runtime assertion failures on the way to
// termination can be properly captured.
func SetupRedactionAndStderrRedirects() (cleanupForTestingOnly func(), err error) {
// The general goal of this function is to set up a secondary logger
// to capture internal Go writes to os.Stderr / fd 2 and redirect
// them to a separate (non-redactable) log file, This is, of course,
// only possible if there is a log directory to work with -- until
// we extend the log package to use e.g. network sinks.
//
// In case there is no log directory, we must be careful to not
// enable log redaction whatsoever.
//
// This is because otherwise, it is possible for some direct writer
// to fd 2, for example the Go runtime when processing an internal
// assertion error, to interleave its writes going to stderr
// together with a logger that wants to insert log redaction markers
// also on stderr. Because the log code cannot control this
// interleaving, it cannot guarantee that the direct fd 2 writes
// won't be positioned outside of log redaction markers and
// mistakenly considered as "safe for reporting".
// Sanity check.
if active, firstUse := IsActive(); active {
panic(errors.Newf("logging already active; first use:\n%s", firstUse))
}
if mainLog.logDir.IsSet() {
// We have a log directory. We can enable stderr redirection.
// Our own cancellable context to stop the secondary logger.
//
// Note: we don't want to take a cancellable context from the
// caller, because in the usual case we don't want to stop the
// logger when the remainder of the process stops. See the
// discussion on cancel at the top of the function.
ctx, cancel := context.WithCancel(context.Background())
secLogger := NewSecondaryLogger(ctx, &mainLog.logDir, "stderr",
true /* enableGC */, true /* forceSyncWrites */, false /* enableMsgCount */)
// This logger will capture direct stderr writes.
secLogger.logger.redirectInternalStderrWrites = true
// Stderr capture produces unsafe strings. This logger
// thus generally produces non-redactable entries.
secLogger.logger.redactableLogs.Set(false)
// Force a log entry. This does two things: it forces
// the creation of a file and the redirection of fd 2 / os.Stderr.
// It also introduces a timestamp marker.
secLogger.Logf(ctx, "stderr capture started")
prevStderrLogger := stderrLog
stderrLog = &secLogger.logger
// The cleanup fn is for use in tests.
cleanup := func() {
// Restore the apparent stderr logger used by Shout() and tests.
stderrLog = prevStderrLogger
// Cancel the gc process for the secondary logger.
cancel()
// Close the logger.
secLogger.Close()
}
// Now that stderr is properly redirected, we can enable log file
// redaction as requested. It is safe because no interleaving
// is possible any more.
mainLog.redactableLogs.Set(redactableLogsRequested)
return cleanup, nil
}
// There is no log directory.
// If redaction is requested and we have a chance to produce some
// log entries on stderr, that's a configuration we cannot support
// safely. Reject it.
if redactableLogsRequested && mainLog.stderrThreshold.get() != Severity_NONE {
return nil, errors.New("cannot enable redactable logging without a logging directory")
}
// Configuration valid. Assign it.
// (Note: This is a no-op, because either redactableLogsRequested is false,
// or it's true but stderrThreshold filters everything.)
mainLog.redactableLogs.Set(redactableLogsRequested)
return nil, nil
}
// We use redactableLogsRequested instead of mainLog.redactableLogs
// directly when parsing command-line flags, to prevent the redactable
// flag from being set until SetupRedactionAndStderrRedirects() has
// been called.
//
// This ensures that we don't mistakenly start producing redaction
// markers until we have some confidence they won't be interleaved
// with arbitrary writes to the stderr file descriptor.
var redactableLogsRequested bool
// TestingResetActive clears the active bit. This is for use int tests
// that call stderr redirection alongside other tests that use
// logging.
func TestingResetActive() {
logging.mu.Lock()
defer logging.mu.Unlock()
logging.mu.active = false
}