mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-10-12 01:58:46 +02:00
The journal records the network state of the host and each change of that state. The support team reads one file to see the interfaces, the routes, the resolvers, and the recoveries around an incident. The same file shows the health of the query path. A change that touches only AirDrop or virtual interfaces runs no DNS work.
71 lines
2.5 KiB
Go
71 lines
2.5 KiB
Go
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"regexp"
|
|
|
|
"github.com/rs/zerolog"
|
|
"github.com/Control-D-Inc/ctrld"
|
|
)
|
|
|
|
// journalField marks an event for the retained journal stream. Both packages
|
|
// share the name, so one writer keeps the lines of both.
|
|
const journalField = ctrld.JournalField
|
|
|
|
// journalMarker is the serialized form of the journal field. The journal
|
|
// writer keeps a line below warn level only when the line contains it.
|
|
var journalMarker = []byte(`"` + journalField + `":true`)
|
|
|
|
// retainedURLPath matches the part of a URL that follows the host. The class
|
|
// stops the match at the characters that end a value inside a JSON line.
|
|
var retainedURLPath = regexp.MustCompile(`(https?://[^/?"\\\s]+)[/?][^"\\\s]*`)
|
|
|
|
// redactRetainedLine strips the secrets of one line that the journal keeps.
|
|
// Support downloads the journal whole, and any error text can carry a DoH URL
|
|
// with the resolver path and the packed query of the user.
|
|
func redactRetainedLine(p []byte) []byte {
|
|
stripped := retainedURLPath.ReplaceAll(p, []byte(`${1}/[redacted]`))
|
|
return []byte(redactSecrets(string(stripped), provisionSecrets()...))
|
|
}
|
|
|
|
// journal marks an event for the retained journal stream. The event keeps
|
|
// its own level, so an info event stays info on the console.
|
|
func journal(e *zerolog.Event) *zerolog.Event {
|
|
return ctrld.Journal(e)
|
|
}
|
|
|
|
// journalLevelWriter passes on the lines that the journal keeps and drops the
|
|
// rest, so the journal holds a small history that survives a restart.
|
|
type journalLevelWriter struct {
|
|
w io.Writer
|
|
}
|
|
|
|
var _ zerolog.LevelWriter = (*journalLevelWriter)(nil)
|
|
|
|
func newJournalLevelWriter(w io.Writer) *journalLevelWriter {
|
|
return &journalLevelWriter{w: w}
|
|
}
|
|
|
|
// WriteLevel reads the level first, because a compare costs less than a scan
|
|
// of the serialized line.
|
|
func (j *journalLevelWriter) WriteLevel(l zerolog.Level, p []byte) (int, error) {
|
|
if l < zerolog.WarnLevel && !bytes.Contains(p, journalMarker) {
|
|
// A dropped line reports a full write, because zerolog.MultiLevelWriter
|
|
// turns a short write into an error for the whole group of writers.
|
|
return len(p), nil
|
|
}
|
|
if _, err := j.w.Write(redactRetainedLine(p)); err != nil {
|
|
return 0, err
|
|
}
|
|
// The redacted line is shorter than the line the logger handed over, and a
|
|
// short write is an error for the whole group of writers.
|
|
return len(p), nil
|
|
}
|
|
|
|
// Write has no level to read, so the marker alone decides. Every level below
|
|
// warn takes that path.
|
|
func (j *journalLevelWriter) Write(p []byte) (int, error) {
|
|
return j.WriteLevel(zerolog.DebugLevel, p)
|
|
}
|