Sharing loggers across contexts and packages

agolangf · · 491 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>How do you all manage sharing a logger between contexts /packages? Particularly in the case where the the standard log package is not sufficient.</p> <p>I worked for a Googler on a large go project for a while, they&#39;d written a library much like the context package, but it also provided a threadsafe map[interface{}]interface{} that was used, among other things, to store a configured logger so that it could be shared. We just wrote a convenience function like <code>func Logger(ctx context.Context) *log.Logger</code> that used a const key and a type assertion to pull out the logger. I personally liked using that (and I continue to use that package in my own projects) but I&#39;m curious as to the other methods out there.</p> <p>I&#39;d prefer in general to avoid globals/singletons, fwiw, but I suppose I could be convinced otherwise. </p> <hr/>**评论:**<br/><br/>fortytw2: <pre><p>Don&#39;t use context.Context to pass shared state, i.e. loggers or database connections </p> <p>Just explicitly pass a Logger interface around (like the go-kit log interface, or log15 does), as it&#39;s an explicit dependency of your functions and should be treated as such</p></pre>disq: <pre><p>Accept/use interfaces. But be careful, some log packages (like github.com/alexcesaro/log/stdlog) use their custom types in interface definitions which don&#39;t really work if any of the packages are using vendor/. So, the most usable interface definitions are the ones which only use native types. If none of the available interfaces suit the use case, just define your own.</p></pre>comradeswitch: <pre><p>I like this idea. Could you elaborate on how vendoring can break those things? I&#39;ve got some experience with it but haven&#39;t run into any issues. Is it an issue where the vendored interface definition ends up requiring the custom type from the original (i.e., non-vendored) package?</p></pre>disq: <pre><p>Examine <a href="https://github.com/alexcesaro/log/blob/master/log.go#L81" rel="nofollow">this</a>. The interface requires a custom-defined <code>Level</code> type. But, if you were to include this package in your project&#39;s vendor dir, the <code>log.Level</code> type would be referred to as <code>/path-to/your-package/vendor/github.com/alexcesaro/log.Level</code>. Another package (using <em>any</em> logger including this one) can&#39;t satisfy this interface. Because, even if they&#39;re also using this <code>log</code>, their <code>log.Level</code> would be either under <code>GOPATH</code> (if they&#39;re not using vendoring) or their own vendor directory, under their own package dir. My solution in this case was, since I was controlling both of the packages, I created another Log interface without the Level stuff. But if any of the packages I used were to refer to the <code>alexcesaro/log/Logger</code> interface, I would have no other option than to give up vendoring and require the logger dependency to be installed in GOPATH.</p></pre>comradeswitch: <pre><p>aha. Yeah, that would be an issue.</p> <p>If it were critical, I&#39;d be tempted to just fork the dependency in question and install it in GOPATH, I think. Rebasing my fork to get an update would be no more work than updating a vendored package as far as I can tell. Anything I&#39;m missing with that?</p></pre>disq: <pre><p>AFAICT it would work, as long as all the log-using packages were also expecting the logger/interface in the GOPATH (not vendoring their own)</p></pre>comradeswitch: <pre><p>Ah, yeah, that&#39;s a good point- thanks. </p></pre>very-little-gravitas: <pre><p>What problems have you seen with package globals? Having tried a few approaches (attaching to a server global, passing a logger in to handlers as you suggest here), I quite like the package approach (behind a mutex if necc.) as everyone who wants to log just pulls in the log pkg and the dependency is very clear. On startup the app configs logs, after startup handlers etc import the log pkg and call its functions to use the default loggers. </p> <p>Isn&#39;t passing in a map like that really just passing a singleton logger via the context?</p></pre>comradeswitch: <pre><p>It&#39;s not necessarily a singleton- if there&#39;s a need to provide a different logger to portions of the program, you can swap it out in the fork of the context given to that portion which will swap the logger out for all children.</p> <p>That said, I do see your point. </p> <p>How would you handle something like different components of an app, say, prefixing the log statement with a different string? (I&#39;m a fan of structured logging and often include a field for which component generated the message, a similar problem) Using the standard log package, that would require manually adding the prefix to each log message, creating some set of wrapper functions, or using multiple loggers.</p></pre>very-little-gravitas: <pre><p>I wasn&#39;t suggesting using the std log pkg, sorry that wasn&#39;t clear. </p> <p>The approach I&#39;m taking at the moment is a single log pkg, which is called on startup with log.Add(log.NewStdErr) and log.Add(MyStatsLogger) - this adds to an internal chain of loggers which will receive log messages. Then those who want to send to the log import log, and call something like log.Log(data map[string]interface{}), which accepts a map of inputs to send to all registered loggers.</p> <p>So a sort of simplistic pub/sub approach - the app sets up subscribers (loggers), and the handlers/middleware publish to the logs. Publishers don&#39;t know about subscribers, they just log, and subscribers can filter messages so they might look at keys like level or sender (if you want to tag them with a sending pkg). For tests you can set up a simpler logger which writes to a buffer which you can read before running tests, again using the same pkg. </p> <p>I don&#39;t see much advantage to threading what are in the end often global variables/singletons through the context. I&#39;ve never needed to change the logger for all children. The same thing happens with dependencies like db access - having tried threading various dependencies through a context (both my own and stdlib), I now tend to isolate those dependencies in packages and import them directly where they are used. It&#39;s clearer and probably faster too. </p></pre>comradeswitch: <pre><p>Hey no problem, thanks for the detailed explanation. I&#39;m going to give this a try with my next personal project, it&#39;s definitely appealing and would simplify a lot of things. That&#39;s a good point re:db access and the like, I&#39;ve spent quite a bit of time trying to handle that sort of thing through contexts and...well, I&#39;m ready for a different approach. Thanks again!</p></pre>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

491 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传