-
Notifications
You must be signed in to change notification settings - Fork 221
/
context.go
113 lines (96 loc) · 3.57 KB
/
context.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
package statuslogger
import (
"context"
"fmt"
"os"
"github.com/superfly/flyctl/internal/buildinfo"
"github.com/superfly/flyctl/iostreams"
)
type contextKey struct{}
// NewContext derives a Context that carries sl from ctx.
func NewContext(ctx context.Context, sl StatusLine) context.Context {
if buildinfo.IsDev() && FromContextOptional(ctx) != nil && os.Getenv("FLYCTL_STATUSLOGGER_NO_ERROR") == "" {
panic("attempting to create a new statuslogger context when the parent context already has a logger! This is a bug.")
}
return context.WithValue(ctx, contextKey{}, sl)
}
// FromContext returns the StatusLine ctx carries. It panics in case ctx carries
// no StatusLine.
func FromContext(ctx context.Context) StatusLine {
return ctx.Value(contextKey{}).(StatusLine)
}
// FromContextOptional returns the StatusLine ctx carries if any, or nil.
func FromContextOptional(ctx context.Context) StatusLine {
val := ctx.Value(contextKey{})
if val == nil {
return nil
}
return ctx.Value(contextKey{}).(StatusLine)
}
// Shorthands for FromContext(ctx).Foo()
func releaseFallback(ctx context.Context, format string, args ...interface{}) bool {
// For debug builds, we'll let logging functions crash if we haven't set up a status logger.
// In release builds, we'll log to stdout instead of crashing so we don't break things for users.
//
// In a perfect world, this would be a Very Temporary hack, but it'll probably stick around a while just in case.
if FromContextOptional(ctx) == nil {
if buildinfo.IsRelease() || os.Getenv("FLYCTL_STATUSLOGGER_NO_ERROR") != "" {
// TODO(Ali): It'd probably be good to have metrics or sentry here.
fmt.Fprintf(iostreams.FromContext(ctx).Out, format+"\n", args...)
return true
} else {
panic("Tried to log to a status logger that doesn't exist! This is a bug and crashes debug builds.\nUse FLYCTL_STATUSLOGGER_NO_ERROR=1 to ignore this for now.")
}
}
return false
}
// Log into the StatusLine ctx carries. Panics if ctx doesn't contain a StatusLine.
func Log(ctx context.Context, s string) {
if releaseFallback(ctx, "%s", s) {
return
}
FromContext(ctx).Log(s)
}
// Logf into the StatusLine ctx carries. Panics if ctx doesn't contain a StatusLine.
func Logf(ctx context.Context, format string, args ...interface{}) {
if releaseFallback(ctx, format, args...) {
return
}
FromContext(ctx).Logf(format, args...)
}
// LogStatus into the StatusLine ctx carries. Panics if ctx doesn't contain a StatusLine.
func LogStatus(ctx context.Context, s Status, str string) {
if releaseFallback(ctx, "%s", s) {
return
}
FromContext(ctx).LogStatus(s, str)
}
// LogfStatus into the StatusLine ctx carries. Panics if ctx doesn't contain a StatusLine.
func LogfStatus(ctx context.Context, s Status, format string, args ...interface{}) {
if releaseFallback(ctx, format, args...) {
return
}
FromContext(ctx).LogfStatus(s, format, args...)
}
// Failed marks the current line as failed, and prints *the first line* of the error provided.
// The assumption is that the full error will be printed elsewhere.
// Panics if ctx doesn't contain a StatusLine.
func Failed(ctx context.Context, e error) {
if releaseFallback(ctx, "") {
return
}
FromContext(ctx).Failed(e)
}
// Pause clears the status lines and prevents redraw until the returned resume function is called.
// This allows you to write multiple lines to the terminal without overlapping the status area.
func Pause(ctx context.Context) (ret ResumeFn) {
ret = func() {}
line := FromContextOptional(ctx)
if line == nil {
return
}
if il, ok := line.(*interactiveLine); ok {
return il.logger.Pause()
}
return
}