-
Notifications
You must be signed in to change notification settings - Fork 64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: handle stderr and stdout messages from plugins #1258
Conversation
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## main #1258 +/- ##
==========================================
+ Coverage 61.60% 64.07% +2.46%
==========================================
Files 98 98
Lines 6248 5038 -1210
==========================================
- Hits 3849 3228 -621
+ Misses 2066 1476 -590
- Partials 333 334 +1 ☔ View full report in Codecov by Sentry. |
I'll get those whitespaces cleaned up today before the community call :) |
"Please describe the tests that you ran to verify your changes. Please also list any relevant details for your test configuration" , Some scenarios i have in mind are ( please choose what you think is relevant):
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks so much for improving the plugin experience, change looks great overall, i left some minor comments.
plugins/verifier/sample/sample.go
Outdated
@@ -37,7 +38,14 @@ type PluginInputConfig struct { | |||
} | |||
|
|||
func main() { | |||
// send info and debug messages to stderr | |||
fmt.Fprintln(os.Stderr, "info: initialized plugin") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wondering if the following statement is true? "stdOut should only be used for VerifierReport response, all informational should be written to stderr". What is the best place to document this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not entirely true. 😅I wrote the feature to handle multiple messages form stdout and stderr that way plugin developers could write to either. The comment is there to explain what the following 2 lines are doing, so maybe it's more accurate to say "sends info and debug messages to stderr" I could also add that it's acceptable to also write to stdout.
I'm open to suggestions though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@duffney does this mean the user could do something like this: fmt.Fprintln(os.Stdout, "info: initialized plugin")
and it would work equivalently?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently only stderr adds the extra information to stderr messages. But that makes the assumption that people always put those messages there and no in stdout, which isn't always the case.
I'll start looking at the ratify logger to working on use that to display the error messages.
QQ: is it important to distinguish between info, debug, and warn etc?
If not, then I can just return the message sent by the plugin with the ratify logger and remove the switch that adds the log prefix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be nice if we could. We have issues already where the logs are just too verbose and being able to only display info vs debug will be very helpful.
pkg/common/plugin/exec.go
Outdated
switch { | ||
case strings.HasPrefix(strings.ToLower(line), "info"): | ||
line = strings.Replace(line, "info: ", "", -1) | ||
logrus.Infof("[Plugin] %s", line) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: Instead of using the logrus
package directly, can we consider using the ratify internal context logger found here. Basically, this is an implementation that uses the context
passed down to store extra information like the correlationid. and the GetLogger
method allows the component to be specified making it match the format of other logs. The correlationid is particular helpful to correlate which plugin logs correspond to which http request. You can follow an example hereof how to fetch the logger and use it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cc: @binbin-li
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, so I could pass the context that's passed to the ExecutePlugin
function to the internal logger to provide that additional information? I'll start working on replace logrus with this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
QQ: Should I add another compontent type for plugin, or use the executor type for the logopt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can introduce a new component type called plugin
or something similar
pkg/common/plugin/exec.go
Outdated
|
||
jsonString, _ := json.Marshal(obj) | ||
resultsBuffer.WriteString(string(jsonString)) | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would we need to add the same debug vs info vs warning etc checks for os.Stdout
as well?
plugins/verifier/sample/sample.go
Outdated
@@ -37,7 +38,14 @@ type PluginInputConfig struct { | |||
} | |||
|
|||
func main() { | |||
// send info and debug messages to stderr | |||
fmt.Fprintln(os.Stderr, "info: initialized plugin") | |||
fmt.Fprintf(os.Stderr, "debug: plugin %s %s \n", "sample", "1.0.0") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: It would be nice if we had a plugin logger that could be used here instead of having to know the exact syntax to append to the front of the logs debug:
or info:
for the parsing to work.
Idea would be that if a plugin author wanted to info log something: pluginlogger.Info("blah blah blah")
and the logger would translate to --> fmt.Print(os.Stdout, "info: " + inputString)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Otherwise, there's more burden on plugin authors and we will need to have some good documentation about this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great idea, that makes a lot of sense. In your opinion, where's a good place to put this package? Or should it just be added to the skel package?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah good question. My opinion would be to stick it in pkg/common/plugin. That way it can be used by verifier + store + (cert store very soon)
skel.PluginMain("sample", "1.0.0", VerifyReference, []string{"1.0.0"}) | ||
|
||
// set output to stdout | ||
pluginlogger.SetOutput(os.Stdout) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wondering when should we use std out vs err
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I've seen stderr is the default for messages, but most logging packages provide the option for redirecting to stdout. Tbh, I don't know which is best but wanted to give the flexibility to use both like logrus does. If the team has a strong opinion, I can add comments to nudge people to use stderr, which is the default for the pluginlogger I added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, thanks for the clarification. :) if we could just add these comment to the sample would be good. Exactly what you have mentioned. " stderr is the default for messages, but we do provide the option to redirecting to stdout"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments added to the latest commit. :) Thanks for the feedback!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks so much for the improvements!
|
||
func NewLogger() *Logger { | ||
return &Logger{ | ||
infoLogger: log.New(os.Stderr, "INFO: ", 0), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ratify could swith formatter between text, json and logStash: https://github.com/deislabs/ratify/blob/main/internal/logger/logger.go#L135
just curious if this logger would switch accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @binbin-li, thanks for the comment. I think that would be a good thing to add to match the logging for the rest of the project.
@susanshi @akashsinghal, what do you both think? Worth adding in for this PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest to log an issue to track this improvement for the plugin log so we could merge this one first? what do you think @binbin-li
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logged an issue to keep track of this feature request. :) #1304
line := scanner.Text() | ||
// Assumes a single JSON object is returned somewhere in the output | ||
// does not handle multiple JSON objects | ||
if strings.Contains(line, "{") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just curious if this could handle log like:
info: test value: {some value}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question. The short answer is no. However, I did write code that can handle it. I'm sure it could be improved, but here's what I had come up and then decided against including it to keep it simpler.
var obj interface{} // Change: Use a generic object
var resultb bytes.Buffer
scanner := bufio.NewScanner(&outb)
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, "{") {
err := json.NewDecoder(strings.NewReader(line)).Decode(&obj)
if err != nil {
fmt.Println("couldn't decode entire line as JSON....")
fi := strings.Index(line, "{")
li := strings.LastIndex(line, "}")
if fi != -1 || li != -1 {
err := json.NewDecoder(strings.NewReader(line[fi : li+1])).Decode(&obj)
if err != nil {
fmt.Println("failed to decode JSON: ", err)
}
pre := line[:fi]
post := line[li+1:]
if pre != "" {
fmt.Println(pre)
}
jsonString, _ := json.Marshal(obj)
resultb.WriteString(string(jsonString))
if post != "" {
fmt.Println("post: ", post)
}
}
continue
}
jsonString, _ := json.Marshal(obj)
resultb.WriteString(string(jsonString))
} else {
fmt.Printf("Non-JSON line: %s\n", line)
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Thanks for the contributions @duffney
You're most welcome. Thank you everyone for your time and help in getting this feature added! :) |
Description
What this PR does / why we need it:
Improves the plugin experience by processing additional messages that are sent to
stderr
andstdout
.Which issue(s) this PR
fixes #1022
Type of change
Please delete options that are not relevant.
main
branch)How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Please also list any relevant details for your test configuration
Checklist:
Post Merge Requirements
Helm Chart Change