cyucelen / marker

🖍️ Marker is the easiest way to match and mark strings for colorful terminal outputs!
MIT License
48 stars 13 forks source link

Create logger functionality #17

Closed jnatalzia closed 5 years ago

jnatalzia commented 5 years ago

[WIP]

Thinking about this project, it stands to reason that one of the strongest use cases would be for users to colorize their logs for easy reading. With that in mind, the current setup where you create a builder, give it a string, then mark means that there is a decent amount of overhead to integrating it with a logging system.

I was messing around with the testing framework and the fact that the testing instances have a .Log function got me thinking about wrapping marker in a similar way. There could be some CreateLogger which you could pass a builder to (or maybe it is the builder) which then logs in the colorized manner you specify.

cyucelen commented 5 years ago

Wow, the idea sounds cool! I want to help you on this feature as you start to implementation. It will be lot of fun to see colorful log streams!

jnatalzia commented 5 years ago

Sounds great! Would love your thoughts on how to be integrate with the existing code base. I'm not too familiar with it but from my light experience with it, it seems like separating a logger class from the builder would be a nice separator and still allow folks to use just the builder if they're not trying to color all the logs.

cyucelen commented 5 years ago

Yeah Logger's itself can be implemented with builder pattern to accumulate matcherFuncs in struct, we can simply wrap golangs logger functions to inject colorized strings to logger. I will experiment some abstractions asap.

jnatalzia commented 5 years ago

👍

cyucelen commented 5 years ago

Joe, I want to hear about your suggestions for managing the repository. Should we create a new branch for new features/bug fixes? Or should we go on master simply?

jnatalzia commented 5 years ago

Heya!

Yeah I think new branches make the most sense. It'll help to have the code sectioned out for reviews as well as open the opportunity to parallelize any feature development!

If you want to make a branch with even some pseudo-code of how you see this logger wrapper working, I'd be more than happy to weigh in on it and collaborate.

jnatalzia commented 5 years ago

Hey @cyucelen have you had a chance to look into this? I'm happy to take a crack at it this weekend if you've been busy!

cyucelen commented 5 years ago

I was planning to dive in this weekend. Go ahead and start if you have time earlier than me.

cyucelen commented 5 years ago

Just created a new branch log-marker and started to implement logger functionality.

My idea is wrapping the Golang's logger with our logger. So, implemented the Print function for inspiration. We can wrap others Println, Printf etc...

You can find a better name for LogMarkerRule and can change the current abstraction I made.

jnatalzia commented 5 years ago

Had a thought after browsing some other implementations. Rather than wrapping fmt which it seems you're thinking, what do you think about wrapping the log library?

This link looked promising: https://snippets.aktagon.com/snippets/795-a-simple-log-wrapper-for-go

The additional metadata is great for features like mark surrounded or regex vs fmt which is going to print the raw line

cyucelen commented 5 years ago

Yes, indeed i wrapped log library! Take a look at my commits.

jnatalzia commented 5 years ago

Oh nice! Will give a look. What do you think about opening a work in progress (draft) pr to make comments against?

cyucelen commented 5 years ago

Draft PR opened! Waiting for your comment to add you as assignee.

cyucelen commented 5 years ago

@jnatalzia also what do you think about being collaborator to the marker!

jnatalzia commented 5 years ago

Looking now! Also I'd be happy to be a collaborator 🙂

Darkclainer commented 5 years ago

There are many various libraries that helps logging things, but most of them have one thing in common - they write information using io.Writer interface. And builtin log actually does the same thing - you can control where it writes using log.SetOutput. So, we can make more general solution to this problem. We can make wrapper to io.Writer interface that colourize its input and writes it to old Writer. Rough, incorrect code that just illustrate my idea:

type ColorWriter struct {
    Out io.Writer
    // some fields 
}
func (cw *ColorWriter) Write(p []byte) (n int, err error) {
    // convert p to string, maybe we should wait until \n and collect parts
    s := ...
    // colorize
    marked = Mark(marked, rule.Matcher, rule.Color)
    return cw.Out.Write([]byte(marked))
}
// for logger
log.SetOutput(ColorWriter{stdout})

Of course there are some drawbacks. For example we can not control colors for different log methods. Question is: what actually do we want?

jnatalzia commented 5 years ago

Oooo very interesting.

For example we can not control colors for different log method

Do you mean different places to write to? As in colorizing only matters in a stdout case?

Darkclainer commented 5 years ago

Do you mean different places to write to? As in colorizing only matters in a stdout case?

Actually it does. If logging is happening to file - colors will be in the way later. But it wasn't a point, I completely forgot about case you mentioned, but it is also a very good remark.

I can't imagine why we should have different behaviour for different log methods, so we will end in just wrapping all methods of log.

cyucelen commented 5 years ago

But anyways, we can implement io.Writer interface to pass marker.StdoutWriter to log.SetOutput instead of wrapping all methods of log right?

type StdoutWriter struct {
    rules  []MarkRule
    Out    io.Writer
}
...
// AddRule methods...
...
func (s StdoutWriter) Write(p []byte) (n int, err error) {
    marked := string(p)
    for _, rule := range s.rules {
        marked = Mark(marked, rule.Matcher, rule.Color)
    }
    return l.Out.Write([]byte(marked))
}
red := color.New(color.FgRed)
stdoutMarker := marker.NewStdoutMarker() // sets Out to os.stdout
stdoutMarker.AddRule(marker.MarkRule{marker.MatchBracketSurrounded(), red})
logger := log.New(stdoutMarker, "", 0)
logger.Println("[you] or not [me]")

This is just for logging to stdout, we can implement another writer for another use case - most probably we will end up with implementing(when the day comes) another Mark function for another usecases too. Like MarkHTML, MarkPDF, put your imagination here...

cyucelen commented 5 years ago

I implemented the io.Writer interface that we discussed. I think it is super easy and awesome now!

We simply reversed the flow from marker.Logger -> log.Logger -> os.Stdout to log.Logger -> marker.StdoutMarker -> os.Stdout

By this way instead of manipulating and passing the text to downstream log.Logger, we are just getting the final output of logger; manipulating it and writing to stdout.

This also makes us pluggable to existing codebases! :tada: