<p>Hello,
go newbie here. I've got a program that uses several (20+) goroutines to handle fetching data, processing, and handling. Each routine is for{}. This all works great. The problem is reading the stdout.</p>
<p>Right now, every routine just does log.Printf/Println with a prefix string so I can identify each by name. As you can imagine, all of the lines are intermixed. I may see ABA, BCA, ABC. What I want is to see AAA,BBB,CCC. Essentially, buffering/locking the output and only printing it to stdout when that iteration of that routine's loop is done.</p>
<p>Is this buffering the correct approach over using sync.RWMutex? Does anyone have any sample code on this implementation?</p>
<p>Thanks!</p>
<hr/>**评论:**<br/><br/>sethammons: <pre><p>You could have them all share a channel and they could write to the channel. A single for-select loop or range over the channel to pull out output is an option. You could pass through a struct of {name string; log string} and collect the the output however you like. Or a buffer as you proposed. Lots of options. </p></pre>utdrmac: <pre><p>I like this, but still seems like it would still be random/interwoven output. Would I have to add some sort of "flush" method/action through the channel to inform the logger that it can now print all of that particular 'name's buffered up contents?</p></pre>sethammons: <pre><p>Could do that. It feels a bit more over-engineered than just having buffers in each goroutine. But, yeah, you could have the struct passed on the channel have a flush bool attribute. </p></pre>qu33ksilver: <pre><p>No need to use a mutex. Send the log lines over a channel and start appending them to a <code>map[prefix][]string</code>. Have a ticker running, which at certain intervals flush the map by writing the values to stdout/file.</p>
<pre><code>ticker := time.NewTicker(flushIntervalSecs * time.Second)
for {
select {
case <-ticker.C:
// flush the map, write to target
case logLine := <-logChan:
// add to map
}
}
</code></pre>
<p>The writes and reads on the map are automatically serialized because of the <code>for-select</code> ! You can make the channel buffered if you don't want your application to block during the flush is going on. But then while reading from the map, you have to do some juggling around.</p></pre>allhatenocattle: <pre><p>I think having each goroutine write to a *bytes.Buffer and then logging at the end would be the best way to group the lines together. One downside is that if a routine is stuck in the middle of something you wouldn't see any output to help debug it.</p></pre>utdrmac: <pre><p>Thanks. This was my original thought as well. Just wondering if there was "a better way." Have you seen any existing libraries/code that does this so I could take a look?</p></pre>allhatenocattle: <pre><p>Here is an example:</p>
<pre><code>package main
import (
"bytes"
"fmt"
"strconv"
"sync"
)
func yourfunc(message string, w *sync.WaitGroup) {
defer w.Done()
b := &bytes.Buffer{}
defer fmt.Print(b)
fmt.Fprintf(b, "starting yourfunc with %s\n", message)
fmt.Fprintf(b, "message is %s\n", message)
fmt.Fprintf(b, "finished yourfunc with %s\n", message)
}
func main() {
w := &sync.WaitGroup{}
messages := make([]string, 0)
for i := 0; i < 100; i++ {
messages = append(messages, strconv.Itoa(i))
}
for _, m := range messages {
w.Add(1)
go yourfunc(m, w)
}
w.Wait()
}
</code></pre>
<p>When I run that on my machine, the goroutine finish order is kinda random, but all the lines are together:<br/>
...<br/>
starting yourfunc with 57
message is 57<br/>
finished yourfunc with 57<br/>
starting yourfunc with 77<br/>
message is 77<br/>
finished yourfunc with 77<br/>
starting yourfunc with 78<br/>
message is 78<br/>
finished yourfunc with 78<br/>
starting yourfunc with 72<br/>
message is 72<br/>
finished yourfunc with 72<br/>
starting yourfunc with 79<br/>
message is 79<br/>
finished yourfunc with 79<br/>
starting yourfunc with 73<br/>
message is 73<br/>
finished yourfunc with 73<br/>
... </p>
<p>[edit: formatting]</p></pre>Femaref: <pre><p>yes, buffering is the best choice here. if you were using a mutex the only way to see the logs grouped together would be to hold the lock until the iteration is over. If that's the case, your application essentially would have a single go routine. Save the output of each goroutine into a buffer, and write it to the log at the end of the loop.</p></pre>NeoinKarasuYuu: <pre><p>I like <a href="/u/qu33ksilver" rel="nofollow">/u/qu33ksilver</a>'s solution if this is needed for a quick command line application. </p>
<p>But if this is for a server, a slightly more intricate solution might be nice. For a similar usecase (although not strictly logging) I had, I dumped the data into boltDB and exposed the data over grpc API.
Every goroutine's logs goes into a different bucket in boltDB, using the prefix as the key for that bucket and the autogen id as the key for a entry.<br/>
On grpc there are two endpoints. One endpoint to query a list of goroutines, and another one to get the log of one goroutine. Note, with grpc you can stream the new log entries to clients. A client can then be written in any front-end that can do grpc.</p>
<p>The advantage of boltDB+grpc is that multiple devs can connect to to the server and inspect the logs (and search them based on prefix). </p></pre>
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传