pulumi/pkg/util/logging/log.go
Matt Ellis acf0fb278a Fix wierd interactions due to Cobra and glog
The glog package force the use of golang's underyling flag package,
which Cobra does not use. To work around this, we had a complicated
dance around defining flags in multiple places, calling flag.Parse
explicitly and then stomping values in the flag package with values we
got from Cobra.

Because we ended up parsing parts of the command line twice, each with
a different set of semantics, we ended up with bad UX in some
cases. For example:

`$ pulumi -v=10 --logflow update`

Would fail with an error message that looked nothing like normal CLI
errors, where as:

`$ pulumi -v=10 update --logflow`

Would behave as you expect. To address this, we now do two things:

- We never call flag.Parse() anymore. Wacking the flags with values we
  got from Cobra is sufficent for what we care about.

- We use a forked copy of glog which does not complain when
  flag.Parse() is not called before logging.

Fixes #301
Fixes #710
Fixes #968
2018-08-20 14:08:40 -07:00

152 lines
3.9 KiB
Go

// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logging
// Wrapper around the glog API that allows us to intercept all logging calls and manipulate them as
// necessary. This is primarily used so we can make a best effort approach to filtering out secrets
// from any logs we emit before they get written to log-files/stderr.
//
// Code in pulumi should use this package instead of directly importing glog itself. If any glog
// methods are needed that are not exported from this, they can be added, with the caveat that they
// should be updated to properly filter as well before forwarding things along.
import (
"bytes"
"flag"
"fmt"
"regexp"
"strconv"
"sync"
"github.com/golang/glog"
)
type Filter interface {
Filter(s string) string
}
var LogToStderr = false // true if logging is being redirected to stderr.
var Verbose = 0 // >0 if verbose logging is enabled at a particular level.
var LogFlow = false // true to flow logging settings to child processes.
var rwLock sync.RWMutex
var filters []Filter
func V(level glog.Level) glog.Verbose {
return glog.V(level)
}
func Errorf(format string, args ...interface{}) {
glog.Errorf("%s", FilterString(fmt.Sprintf(format, args...)))
}
func Infof(format string, args ...interface{}) {
glog.Infof("%s", FilterString(fmt.Sprintf(format, args...)))
}
func Warningf(format string, args ...interface{}) {
glog.Warningf("%s", FilterString(fmt.Sprintf(format, args...)))
}
func Flush() {
glog.Flush()
}
// InitLogging ensures the logging library has been initialized with the given settings.
func InitLogging(logToStderr bool, verbose int, logFlow bool) {
// Remember the settings in case someone inquires.
LogToStderr = logToStderr
Verbose = verbose
LogFlow = logFlow
// glog uses golang's built in flags package to set configuration values, which is incompatible with how
// we use cobra. So we explicitly set the flags we care about here, and don't call flag.Parse()
if logToStderr {
err := flag.Lookup("logtostderr").Value.Set("true")
assertNoError(err)
}
if verbose > 0 {
err := flag.Lookup("v").Value.Set(strconv.Itoa(verbose))
assertNoError(err)
}
}
func assertNoError(err error) {
if err != nil {
failfast(err.Error())
}
}
func failfast(msg string) {
panic(fmt.Sprintf("fatal: %v", msg))
}
type nopFilter struct {
}
func (f *nopFilter) Filter(s string) string {
return s
}
type regexFilter struct {
re *regexp.Regexp
replacement string
}
func (f *regexFilter) Filter(s string) string {
return f.re.ReplaceAllLiteralString(s, f.replacement)
}
func AddGlobalFilter(filter Filter) {
rwLock.Lock()
filters = append(filters, filter)
rwLock.Unlock()
}
func CreateFilter(secrets []string, replacement string) Filter {
var b bytes.Buffer
for _, secret := range secrets {
// For short secrets, don't actually add them to the filter, this is a trade-off we make to prevent
// displaying `[secret]`. Travis does a similar thing, for example.
if len(secret) < 3 {
continue
}
if b.Len() > 0 {
b.WriteRune('|')
}
b.WriteString(regexp.QuoteMeta(secret))
}
// "[secret]"
if b.Len() > 0 {
return &regexFilter{re: regexp.MustCompile(b.String()), replacement: replacement}
}
return &nopFilter{}
}
func FilterString(msg string) string {
var localFilters []Filter
rwLock.RLock()
localFilters = filters
rwLock.RUnlock()
for _, filter := range localFilters {
msg = filter.Filter(msg)
}
return msg
}