/
log.go
116 lines (101 loc) · 4.04 KB
/
log.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
// 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 provider
import (
"bufio"
"context"
"strings"
"github.com/pulumi/pulumi/pkg/diag"
"github.com/pulumi/pulumi/pkg/resource/provider"
"github.com/pulumi/pulumi/pkg/util/contract"
)
// LogRedirector creates a new redirection writer that takes as input plugin stderr output, and routes it to the
// correct Pulumi stream based on the standard Terraform logging output prefixes.
type LogRedirector struct {
enabled bool // true if standard logging is on; false for debug-only.
writers map[string]func(string) error // the writers for certain labels.
buffer []byte // a buffer that holds up to a line of output.
}
// NewTerraformLogRedirector returns a new LogRedirector with the (unexported) writers field
// set to the given map.
func NewTerraformLogRedirector(ctx context.Context, hostClient *provider.HostClient) *LogRedirector {
return &LogRedirector{
writers: map[string]func(string) error{
tfTracePrefix: func(msg string) error { return hostClient.Log(ctx, diag.Debug, "", msg) },
tfDebugPrefix: func(msg string) error { return hostClient.Log(ctx, diag.Debug, "", msg) },
tfInfoPrefix: func(msg string) error { return hostClient.Log(ctx, diag.Info, "", msg) },
tfWarnPrefix: func(msg string) error { return hostClient.Log(ctx, diag.Warning, "", msg) },
tfErrorPrefix: func(msg string) error { return hostClient.Log(ctx, diag.Error, "", msg) },
},
}
}
const (
tfTracePrefix = "[TRACE]"
tfDebugPrefix = "[DEBUG]"
tfInfoPrefix = "[INFO]"
tfWarnPrefix = "[WARN]"
tfErrorPrefix = "[ERROR]"
)
// Enable turns on full featured logging. This is the default.
func (lr *LogRedirector) Enable() {
lr.enabled = true
}
// Disable disables most of the specific logging levels, but it retains debug logging.
func (lr *LogRedirector) Disable() {
lr.enabled = false
}
func (lr *LogRedirector) Write(p []byte) (n int, err error) {
written := 0
// If a line starts with [TRACE], [DEBUG], or [INFO], then we emit to a debug log entry. If a line starts with
// [WARN], we emit a warning. If a line starts with [ERROR], on the other hand, we emit a normal stderr line.
// All others simply get redirected to stdout as normal output.
for len(p) > 0 {
adv, tok, err := bufio.ScanLines(p, false)
if err != nil {
return written, err
}
// If adv == 0, there was no newline; buffer it all and move on.
if adv == 0 {
lr.buffer = append(lr.buffer, p...)
written += len(p)
break
}
// Otherwise, there was a newline; emit the buffer plus payload to the right place, and keep going if
// there is more.
lr.buffer = append(lr.buffer, tok...) // append the buffer.
s := string(lr.buffer)
// To do this we need to parse the label if there is one (e.g., [TRACE], et al).
var label string
if start := strings.IndexRune(s, '['); start != -1 {
if end := strings.Index(s[start:], "] "); end != -1 {
label = s[start : start+end+1]
s = s[start+end+2:] // skip past the "] " (notice the space)
}
}
w, has := lr.writers[label]
if !has || !lr.enabled {
// If there was no writer for this label, or logging is disabled, use the debug label.
w = lr.writers[tfDebugPrefix]
contract.Assert(w != nil)
}
if err := w(s); err != nil {
return written, err
}
// Now keep moving on provided there is more left in the buffer.
lr.buffer = lr.buffer[:0] // clear out the buffer.
p = p[adv:] // advance beyond the extracted region.
written += adv
}
return written, nil
}