-
Notifications
You must be signed in to change notification settings - Fork 649
/
Copy pathlog_viewer.go
180 lines (143 loc) · 5.35 KB
/
log_viewer.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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
/*
Copyright The containerd Authors.
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
import (
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"github.com/containerd/log"
"github.com/containerd/nerdctl/v2/pkg/labels/k8slabels"
)
// Type alias for functions which write out logs to the provided stdout/stderr Writers.
// Depending on the provided `LogViewOptions.Follow` option, the function may block
// indefinitely until something is sent through the `stopChannel`.
type LogViewerFunc func(lvopts LogViewOptions, stdout, stderr io.Writer, stopChannel chan os.Signal) error
var logViewers = make(map[string]LogViewerFunc)
// Registers a LogViewerFunc for the
func RegisterLogViewer(driverName string, lvfn LogViewerFunc) {
if v, ok := logViewers[driverName]; ok {
log.L.Warnf("A LogViewerFunc with name %q has already been registered: %#v, overriding with %#v either way", driverName, v, lvfn)
}
logViewers[driverName] = lvfn
}
func init() {
RegisterLogViewer("json-file", viewLogsJSONFile)
RegisterLogViewer("journald", viewLogsJournald)
RegisterLogViewer("cri", viewLogsCRI)
}
// Returns a LogViewerFunc for the provided logging driver name.
func getLogViewer(driverName string) (LogViewerFunc, error) {
lv, ok := logViewers[driverName]
if !ok {
return nil, fmt.Errorf("no log viewer type registered for logging driver %q", driverName)
}
return lv, nil
}
// Set of options passable to log viewers.
type LogViewOptions struct {
// Identifier (ID) of the container and namespace it's in.
ContainerID string
Namespace string
// Absolute path to the nerdctl datastore's root.
DatastoreRootPath string
// LogPath specify the log path for container created via CRI
LogPath string
// Whether or not to follow the output of the container logs.
Follow bool
// Whether or not to print timestampts for each line.
Timestamps bool
// Uint representing the number of most recent log entries to display. 0 = "all".
Tail uint
// Start/end timestampts to filter logs by.
Since string
Until string
// Details enables showing extra details(env and label) in logs.
Details bool
// DetailPrefix is the prefix added when Details is enabled.
DetailPrefix string
}
func (lvo *LogViewOptions) Validate() error {
if lvo.ContainerID == "" || lvo.Namespace == "" {
return fmt.Errorf("log viewing options require a ContainerID and Namespace: %#v", lvo)
}
if lvo.DatastoreRootPath == "" || !filepath.IsAbs(lvo.DatastoreRootPath) {
abs, err := filepath.Abs(lvo.DatastoreRootPath)
if err != nil {
return err
}
log.L.Warnf("given relative datastore path %q, transformed it to absolute path: %q", lvo.DatastoreRootPath, abs)
lvo.DatastoreRootPath = abs
}
return nil
}
// Implements functionality for loading the logging configuration and
// fetching/outputting container logs based on its internal LogViewOptions.
type ContainerLogViewer struct {
// Logging configuration.
loggingConfig LogConfig
// Log viewing options and filters.
logViewingOptions LogViewOptions
// Channel to send stop events to the viewer.
stopChannel chan os.Signal
}
// Validates the given LogViewOptions, loads the logging config for the
// given container and returns a ContainerLogViewer.
func InitContainerLogViewer(containerLabels map[string]string, lvopts LogViewOptions, stopChannel chan os.Signal, experimental bool) (contlv *ContainerLogViewer, err error) {
var lcfg LogConfig
if _, ok := containerLabels[k8slabels.ContainerType]; ok {
lcfg.Driver = "cri"
} else {
if err := lvopts.Validate(); err != nil {
return nil, fmt.Errorf("invalid LogViewOptions provided (%#v): %w", lvopts, err)
}
lcfg, err = LoadLogConfig(lvopts.DatastoreRootPath, lvopts.Namespace, lvopts.ContainerID)
if err != nil {
return nil, fmt.Errorf("failed to load logging config: %w", err)
}
}
if lcfg.Driver == "cri" && !experimental {
return nil, fmt.Errorf("the `cri` log viewer requires nerdctl to be running in experimental mode")
}
if lcfg.Driver == "none" {
return nil, fmt.Errorf("log type `none` was selected, nothing to log")
}
lv := &ContainerLogViewer{
loggingConfig: lcfg,
logViewingOptions: lvopts,
stopChannel: stopChannel,
}
return lv, nil
}
// Prints all logs for this LogViewer's containers to the provided io.Writers.
func (lv *ContainerLogViewer) PrintLogsTo(stdout, stderr io.Writer) error {
if lv.logViewingOptions.Details {
prefix := " "
if lv.logViewingOptions.DetailPrefix != "" {
prefix = lv.logViewingOptions.DetailPrefix + " "
}
stdout = NewDetailWriter(stdout, prefix)
stderr = NewDetailWriter(stderr, prefix)
}
viewerFunc, err := getLogViewer(lv.loggingConfig.Driver)
if err != nil {
return err
}
return viewerFunc(lv.logViewingOptions, stdout, stderr, lv.stopChannel)
}
// Convenience wrapper for exec.LookPath.
func checkExecutableAvailableInPath(executable string) bool {
_, err := exec.LookPath(executable)
return err == nil
}