Rewrite log lines on the fly, based on the set of known peers. This enables us to use upstream wireguard-go logging, but maintain the Tailscale-style peer public key identifiers that the rest of our systems (and people) expect. Fixes #1183 Signed-off-by: Josh Bleecher Snyder <josh@tailscale.com>main
parent
4306433d1c
commit
d5baeeed5c
@ -0,0 +1,89 @@ |
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package wglog contains logging helpers for wireguard-go.
|
||||
package wglog |
||||
|
||||
import ( |
||||
"encoding/base64" |
||||
"fmt" |
||||
"strings" |
||||
"sync/atomic" |
||||
|
||||
"github.com/tailscale/wireguard-go/device" |
||||
"github.com/tailscale/wireguard-go/wgcfg" |
||||
"tailscale.com/types/logger" |
||||
) |
||||
|
||||
// A Logger is a wireguard-go log wrapper that cleans up and rewrites log lines.
|
||||
// It can be modified at run time to adjust to new wireguard-go configurations.
|
||||
type Logger struct { |
||||
DeviceLogger *device.Logger |
||||
replacer atomic.Value // of *strings.Replacer
|
||||
} |
||||
|
||||
// NewLogger creates a new logger for use with wireguard-go.
|
||||
// This logger silences repetitive/unhelpful noisy log lines
|
||||
// and rewrites peer keys from wireguard-go into Tailscale format.
|
||||
func NewLogger(logf logger.Logf) *Logger { |
||||
ret := new(Logger) |
||||
|
||||
wrapper := func(format string, args ...interface{}) { |
||||
msg := fmt.Sprintf(format, args...) |
||||
if strings.Contains(msg, "Routine:") { |
||||
// wireguard-go logs as it starts and stops routines.
|
||||
// Drop those; there are a lot of them, and they're just noise.
|
||||
return |
||||
} |
||||
r := ret.replacer.Load() |
||||
if r == nil { |
||||
// No replacements specified; log as originally planned.
|
||||
logf(format, args...) |
||||
return |
||||
} |
||||
// Do the replacements.
|
||||
new := r.(*strings.Replacer).Replace(msg) |
||||
if new == msg { |
||||
// No replacements. Log as originally planned.
|
||||
logf(format, args...) |
||||
return |
||||
} |
||||
// We made some replacements. Log the new version.
|
||||
// This changes the format string,
|
||||
// which is somewhat unfortunate as it impacts rate limiting,
|
||||
// but there's not much we can do about that.
|
||||
logf("%s", new) |
||||
} |
||||
std := logger.StdLogger(wrapper) |
||||
ret.DeviceLogger = &device.Logger{ |
||||
Debug: std, |
||||
Info: std, |
||||
Error: std, |
||||
} |
||||
return ret |
||||
} |
||||
|
||||
// SetPeers adjusts x to rewrite the peer public keys found in peers.
|
||||
// SetPeers is safe for concurrent use.
|
||||
func (x *Logger) SetPeers(peers []wgcfg.Peer) { |
||||
// Construct a new peer public key log rewriter.
|
||||
var replace []string |
||||
for _, peer := range peers { |
||||
old := "peer(" + wireguardGoString(peer.PublicKey) + ")" |
||||
new := peer.PublicKey.ShortString() |
||||
replace = append(replace, old, new) |
||||
} |
||||
r := strings.NewReplacer(replace...) |
||||
x.replacer.Store(r) |
||||
} |
||||
|
||||
// wireguardGoString prints p in the same format used by wireguard-go.
|
||||
func wireguardGoString(k wgcfg.Key) string { |
||||
base64Key := base64.StdEncoding.EncodeToString(k[:]) |
||||
abbreviatedKey := "invalid" |
||||
if len(base64Key) == 44 { |
||||
abbreviatedKey = base64Key[0:4] + "…" + base64Key[39:43] |
||||
} |
||||
return abbreviatedKey |
||||
} |
||||
@ -0,0 +1,59 @@ |
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package wglog_test |
||||
|
||||
import ( |
||||
"fmt" |
||||
"testing" |
||||
|
||||
"github.com/tailscale/wireguard-go/wgcfg" |
||||
"tailscale.com/wgengine/wglog" |
||||
) |
||||
|
||||
func TestLogger(t *testing.T) { |
||||
tests := []struct { |
||||
in string |
||||
want string |
||||
omit bool |
||||
}{ |
||||
{"hi", "hi", false}, |
||||
{"Routine: starting", "", true}, |
||||
{"peer(IMTB…r7lM) says it misses you", "[IMTBr] says it misses you", false}, |
||||
} |
||||
|
||||
c := make(chan string, 1) |
||||
logf := func(format string, args ...interface{}) { |
||||
s := fmt.Sprintf(format, args...) |
||||
select { |
||||
case c <- s: |
||||
default: |
||||
t.Errorf("wrote %q, but shouldn't have", s) |
||||
} |
||||
} |
||||
|
||||
x := wglog.NewLogger(logf) |
||||
key, err := wgcfg.ParseHexKey("20c4c1ae54e1fd37cab6e9a532ca20646aff496796cc41d4519560e5e82bee53") |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
x.SetPeers([]wgcfg.Peer{{PublicKey: key}}) |
||||
|
||||
for _, tt := range tests { |
||||
if tt.omit { |
||||
// Write a message ourselves into the channel.
|
||||
// Then if logf also attempts to write into the channel, it'll fail.
|
||||
c <- "" |
||||
} |
||||
x.DeviceLogger.Info.Println(tt.in) |
||||
got := <-c |
||||
if tt.omit { |
||||
continue |
||||
} |
||||
tt.want += "\n" |
||||
if got != tt.want { |
||||
t.Errorf("Println(%q) = %q want %q", tt.in, got, tt.want) |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue