mirror of https://github.com/pulumi/pulumi.git
250 lines
7.4 KiB
Go
250 lines
7.4 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 diag
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"sync"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
|
|
)
|
|
|
|
// Sink facilitates pluggable diagnostics messages.
|
|
type Sink interface {
|
|
// Logf issues a log message.
|
|
Logf(sev Severity, diag *Diag, args ...interface{})
|
|
// Debugf issues a debugging message.
|
|
Debugf(diag *Diag, args ...interface{})
|
|
// Infof issues an informational message (to stdout).
|
|
Infof(diag *Diag, args ...interface{})
|
|
// Infoerrf issues an informational message (to stderr).
|
|
Infoerrf(diag *Diag, args ...interface{})
|
|
// Errorf issues a new error diagnostic.
|
|
Errorf(diag *Diag, args ...interface{})
|
|
// Warningf issues a new warning diagnostic.
|
|
Warningf(diag *Diag, args ...interface{})
|
|
|
|
// Stringify stringifies a diagnostic into a prefix and message that is appropriate for printing.
|
|
Stringify(sev Severity, diag *Diag, args ...interface{}) (string, string)
|
|
}
|
|
|
|
// Severity dictates the kind of diagnostic.
|
|
type Severity string
|
|
|
|
const (
|
|
Debug Severity = "debug"
|
|
Info Severity = "info"
|
|
Infoerr Severity = "info#err"
|
|
Warning Severity = "warning"
|
|
Error Severity = "error"
|
|
)
|
|
|
|
// FormatOptions controls the output style and content.
|
|
type FormatOptions struct {
|
|
Pwd string // the working directory.
|
|
Color colors.Colorization // how output should be colorized.
|
|
Debug bool // if true, debugging will be output to stdout.
|
|
}
|
|
|
|
// DefaultSink returns a default sink that simply logs output to stderr/stdout.
|
|
func DefaultSink(stdout io.Writer, stderr io.Writer, opts FormatOptions) Sink {
|
|
contract.Requiref(stdout != nil, "stdout", "must not be nil")
|
|
contract.Requiref(stderr != nil, "stderr", "must not be nil")
|
|
|
|
stdoutMu := &sync.Mutex{}
|
|
stderrMu := &sync.Mutex{}
|
|
func() {
|
|
defer func() {
|
|
// The == check below can panic if stdout and stderr are not comparable.
|
|
// If that happens, ignore the panic and use separate mutexes.
|
|
_ = recover()
|
|
}()
|
|
|
|
if stdout == stderr {
|
|
// If stdout and stderr point to the same stream,
|
|
// use the same mutex for them.
|
|
stderrMu = stdoutMu
|
|
}
|
|
}()
|
|
|
|
// Wrap the stdout and stderr writers in a mutex
|
|
// to ensure that we don't interleave output.
|
|
stdout = &syncWriter{Writer: stdout, mu: stdoutMu}
|
|
stderr = &syncWriter{Writer: stderr, mu: stderrMu}
|
|
|
|
// Discard debug output by default unless requested.
|
|
debug := io.Discard
|
|
if opts.Debug {
|
|
debug = stdout
|
|
}
|
|
|
|
return newDefaultSink(opts, map[Severity]io.Writer{
|
|
Debug: debug,
|
|
Info: stdout,
|
|
Infoerr: stderr,
|
|
Error: stderr,
|
|
Warning: stderr,
|
|
})
|
|
}
|
|
|
|
func newDefaultSink(opts FormatOptions, writers map[Severity]io.Writer) *defaultSink {
|
|
contract.Assertf(writers[Debug] != nil, "Writer for %v must be set", Debug)
|
|
contract.Assertf(writers[Info] != nil, "Writer for %v must be set", Info)
|
|
contract.Assertf(writers[Infoerr] != nil, "Writer for %v must be set", Infoerr)
|
|
contract.Assertf(writers[Error] != nil, "Writer for %v must be set", Error)
|
|
contract.Assertf(writers[Warning] != nil, "Writer for %v must be set", Warning)
|
|
contract.Assertf(opts.Color != "", "FormatOptions.Color must be set")
|
|
return &defaultSink{
|
|
opts: opts,
|
|
writers: writers,
|
|
}
|
|
}
|
|
|
|
const DefaultSinkIDPrefix = "PU"
|
|
|
|
// defaultSink is the default sink which logs output to stderr/stdout.
|
|
type defaultSink struct {
|
|
opts FormatOptions // a set of options that control output style and content.
|
|
writers map[Severity]io.Writer // the writers to use for each kind of diagnostic severity.
|
|
}
|
|
|
|
func (d *defaultSink) Logf(sev Severity, diag *Diag, args ...interface{}) {
|
|
switch sev {
|
|
case Debug:
|
|
d.Debugf(diag, args...)
|
|
case Info:
|
|
d.Infof(diag, args...)
|
|
case Infoerr:
|
|
d.Infoerrf(diag, args...)
|
|
case Warning:
|
|
d.Warningf(diag, args...)
|
|
case Error:
|
|
d.Errorf(diag, args...)
|
|
default:
|
|
contract.Failf("Unrecognized severity: %v", sev)
|
|
}
|
|
}
|
|
|
|
func (d *defaultSink) createMessage(sev Severity, diag *Diag, args ...interface{}) string {
|
|
prefix, msg := d.Stringify(sev, diag, args...)
|
|
return prefix + msg
|
|
}
|
|
|
|
func (d *defaultSink) Debugf(diag *Diag, args ...interface{}) {
|
|
// For debug messages, write both to the glogger and a stream, if there is one.
|
|
logging.V(3).Infof(diag.Message, args...)
|
|
msg := d.createMessage(Debug, diag, args...)
|
|
if logging.V(9) {
|
|
logging.V(9).Infof("defaultSink::Debug(%v)", msg[:len(msg)-1])
|
|
}
|
|
d.print(Debug, msg)
|
|
}
|
|
|
|
func (d *defaultSink) Infof(diag *Diag, args ...interface{}) {
|
|
msg := d.createMessage(Info, diag, args...)
|
|
if logging.V(5) {
|
|
logging.V(5).Infof("defaultSink::Info(%v)", msg[:len(msg)-1])
|
|
}
|
|
d.print(Info, msg)
|
|
}
|
|
|
|
func (d *defaultSink) Infoerrf(diag *Diag, args ...interface{}) {
|
|
msg := d.createMessage(Info /* not Infoerr, just "info: "*/, diag, args...)
|
|
if logging.V(5) {
|
|
logging.V(5).Infof("defaultSink::Infoerr(%v)", msg[:len(msg)-1])
|
|
}
|
|
d.print(Infoerr, msg)
|
|
}
|
|
|
|
func (d *defaultSink) Errorf(diag *Diag, args ...interface{}) {
|
|
msg := d.createMessage(Error, diag, args...)
|
|
if logging.V(5) {
|
|
logging.V(5).Infof("defaultSink::Error(%v)", msg[:len(msg)-1])
|
|
}
|
|
d.print(Error, msg)
|
|
}
|
|
|
|
func (d *defaultSink) Warningf(diag *Diag, args ...interface{}) {
|
|
msg := d.createMessage(Warning, diag, args...)
|
|
if logging.V(5) {
|
|
logging.V(5).Infof("defaultSink::Warning(%v)", msg[:len(msg)-1])
|
|
}
|
|
d.print(Warning, msg)
|
|
}
|
|
|
|
func (d *defaultSink) print(sev Severity, msg string) {
|
|
fmt.Fprint(d.writers[sev], msg)
|
|
}
|
|
|
|
func (d *defaultSink) Stringify(sev Severity, diag *Diag, args ...interface{}) (string, string) {
|
|
var prefix bytes.Buffer
|
|
if sev != Info && sev != Infoerr {
|
|
// Unless it's an ordinary stdout message, prepend the message category's prefix (error/warning).
|
|
switch sev {
|
|
case Debug:
|
|
prefix.WriteString(colors.SpecDebug)
|
|
case Error:
|
|
prefix.WriteString(colors.SpecError)
|
|
case Warning:
|
|
prefix.WriteString(colors.SpecWarning)
|
|
case Info, Infoerr:
|
|
// We'll never get here, but the linter doesn't recognize that.
|
|
default:
|
|
contract.Failf("Unrecognized diagnostic severity: %v", sev)
|
|
}
|
|
|
|
prefix.WriteString(string(sev))
|
|
prefix.WriteString(": ")
|
|
prefix.WriteString(colors.Reset)
|
|
}
|
|
|
|
// Finally, actually print the message itself.
|
|
var buffer bytes.Buffer
|
|
buffer.WriteString(colors.SpecNote)
|
|
|
|
if diag.Raw {
|
|
buffer.WriteString(diag.Message)
|
|
} else {
|
|
fmt.Fprintf(&buffer, diag.Message, args...)
|
|
}
|
|
|
|
buffer.WriteString(colors.Reset)
|
|
buffer.WriteRune('\n')
|
|
|
|
// Ensure that any sensitive data we know about is filtered out preemptively.
|
|
filtered := logging.FilterString(buffer.String())
|
|
|
|
// If colorization was requested, compile and execute the directives now.
|
|
return d.opts.Color.Colorize(prefix.String()), d.opts.Color.Colorize(filtered)
|
|
}
|
|
|
|
// syncWriter wraps an io.Writer and ensures that all writes are synchronized
|
|
// with a mutex.
|
|
type syncWriter struct {
|
|
io.Writer
|
|
|
|
mu *sync.Mutex
|
|
}
|
|
|
|
func (w *syncWriter) Write(p []byte) (int, error) {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
return w.Writer.Write(p)
|
|
}
|