{"id":405189,"date":"2024-06-29T18:10:24","date_gmt":"2024-06-29T18:10:24","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=405189"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=405189","title":{"rendered":"<span>Notes about OpenTracing and Logs<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p><strong>1)<\/strong>\u00a0<u>OpenTracing (OT)<\/u>\u00a0<strong>!=<\/strong>\u00a0<u>Logs<\/u>\u00a0but they are very similar.<\/p>\n<p><strong>2)<\/strong>\u00a0Every application has 2 types of scopes: ApplicationScope (AScope) and RequestScope (RScope).<\/p>\n<figure class=\"float\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/d00\/4fd\/3f7\/d004fd3f768a1f59a8b75ec4afb0f5f7.png\" width=\"281\" height=\"300\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/d00\/4fd\/3f7\/d004fd3f768a1f59a8b75ec4afb0f5f7.png\"\/><figcaption><\/figcaption><\/figure>\n<p><u>ApplicationScope<\/u>\u00a0is everything about configs and\u00a0DI.<\/p>\n<p>Think about it as a singleton. This is the thing that starts, does some work, and stops.<\/p>\n<p><u>RequestScope<\/u>\u00a0is everything about user request \/ amqp request \/ etc..<\/p>\n<p>It starts when the application receives\u00a0a request from a client. It encapsulates an execution context.<\/p>\n<p><strong>3)<\/strong>\u00a0What is the difference between\u00a0<u>OpenTracing<\/u>\u00a0and\u00a0<u>Logs<\/u>?<\/p>\n<p><strong>Logs are about\u00a0<u>AScope<\/u>\u00a0and OpenTracing is about\u00a0<u>RScope<\/u>.<\/strong><\/p>\n<p><strong>4)<\/strong>\u00a0Your real application can have more then one\u00a0<u>AScope<\/u>\u00a0at the same time (for example if you are using graceful reload &#8212; create new\/destroy old), but usually, only one\u00a0<u>AScope<\/u>\u00a0exists.<\/p>\n<p><strong>5)\u00a0<\/strong>Logs and OT use the same approach for the messages. This approach called\u00a0<u>structured logging<\/u>.<\/p>\n<p>This approach is very easy. There is only one important thing.\u00a0<strong>Your log message must\u00a0<u>not<\/u>\u00a0contain variable params.\u00a0<\/strong><\/p>\n<p><strong>6)\u00a0<\/strong>Real application has different layers.\u00a0<\/p>\n<p>PrimaryAdapter\u00a0\u2192\u00a0<strong>UseCase\u00a0\u2192 DomainService\u00a0\u2192 DAO<\/strong>\u00a0\u2192 Client (Secondary Adapter) \u2192 ExternalResource<\/p>\n<p>The central part of this chain usually builds by DI.<\/p>\n<p>For example:<\/p>\n<pre><code class=\"go\">type CService struct {}  func NewCService(..., logger logger.ILogger) *CService {}<\/code><\/pre>\n<p>And as you can see above Logger is an AScope object, because it should be created when we build our DI-container.<\/p>\n<p><strong>7)\u00a0Logger is a global object. We should not create a new logger on every request and store it into ctx.<\/strong><\/p>\n<p><strong>8)<\/strong>\u00a0<strong>We should pass context everywhere.<\/strong><\/p>\n<p>Context is the thing that contains our RScope. (function arguments contain request scope too &#8212; because they change between requests)<\/p>\n<p>OpenTracing widely uses context. (<a href=\"https:\/\/github.com\/opentracing\/opentracing-go#creating-a-span-given-an-existing-go-contextcontext\" rel=\"noopener noreferrer nofollow\">https:\/\/github.com\/opentracing\/opentracing-go#creating-a-span-given-an-existing-go-contextcontext<\/a>)<\/p>\n<p>So if you do not pass context &#8212; you do not have OpenTracing.<\/p>\n<p><strong>9)<\/strong>\u00a0Small piece of code.<\/p>\n<p><strong>\/pkg\/logger\/interface.go<\/strong><\/p>\n<pre><code class=\"go\">\/\/ ==== FIELD ==== type Field struct {    Key   string    Value interface{} }  func F(key string, value interface{}) Field {    return Field{Key: key, Value: value} }  \/\/ func FError - returns field based on error  \/\/ ==== LOGGER ==== type Logger interface {    WithFields(with ...Field) Logger     Debug(ctx context.Context, msg string, with ...Field)    Info(ctx context.Context, msg string, with ...Field)    Warn(ctx context.Context, msg string, with ...Field)    Error(ctx context.Context, msg string, with ...Field) }<\/code><\/pre>\n<p><strong>\/pkg\/logger\/context.go<\/strong><\/p>\n<pre><code class=\"go\">type key int  var loggerKey key  func NewContextWithFields(ctx context.Context, with ...Field) context.Context {        fields := make([]Field, 0)    fields = append(fields, fetchFieldsFromContext(ctx)...)    fields = append(fields, with...)     return context.WithValue(ctx, loggerKey, fields) }  func fetchFieldsFromContext(ctx context.Context) []Field {    if fields, ok := ctx.Value(loggerKey).([]Field); ok {       return fields    }     return nil }<\/code><\/pre>\n<p><strong>\/example.go<\/strong><\/p>\n<pre><code class=\"go\">func (uc *UseCase) SomeOperation(ctx context.Context, param uint64) {    span, ctx := opentracing.StartSpanFromContext(ctx, \"operation_name\")    defer span.Finish()      uc.logger.Info(ctx, \"Starting domain operation\", logger.F(\"param\", param))    if err := uc.domainSvc.SomeOperation(ctx, param); err != nil {       uc.logger.Error(ctx, \"Unable to perform domain operation\", logger.FError(err))       return    }    uc.logger.Info(ctx, \"Domain operation finished\") }<\/code><\/pre>\n<p>As you can see above Logger receives the context as the first argument.<\/p>\n<p>Why? &#8212; Because inside of Logger implementation we can define either we need to send this message to OpenTracing collector (if ctx contains span) or just to stdout. (backward compatibility)<\/p>\n<p><strong>10)<\/strong>\u00a0One more thing. We need to process our errors only one time. SO if we return an error we need\u00a0<u>only<\/u>\u00a0return it. We should not log it inside the same function.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/675010\/\"> https:\/\/habr.com\/ru\/articles\/675010\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p><strong>1)<\/strong>\u00a0<u>OpenTracing (OT)<\/u>\u00a0<strong>!=<\/strong>\u00a0<u>Logs<\/u>\u00a0but they are very similar.<\/p>\n<p><strong>2)<\/strong>\u00a0Every application has 2 types of scopes: ApplicationScope (AScope) and RequestScope (RScope).<\/p>\n<figure class=\"float\"><figcaption><\/figcaption><\/figure>\n<p><u>ApplicationScope<\/u>\u00a0is everything about configs and\u00a0DI.<\/p>\n<p>Think about it as a singleton. This is the thing that starts, does some work, and stops.<\/p>\n<p><u>RequestScope<\/u>\u00a0is everything about user request \/ amqp request \/ etc..<\/p>\n<p>It starts when the application receives\u00a0a request from a client. It encapsulates an execution context.<\/p>\n<p><strong>3)<\/strong>\u00a0What is the difference between\u00a0<u>OpenTracing<\/u>\u00a0and\u00a0<u>Logs<\/u>?<\/p>\n<p><strong>Logs are about\u00a0<u>AScope<\/u>\u00a0and OpenTracing is about\u00a0<u>RScope<\/u>.<\/strong><\/p>\n<p><strong>4)<\/strong>\u00a0Your real application can have more then one\u00a0<u>AScope<\/u>\u00a0at the same time (for example if you are using graceful reload &#8212; create new\/destroy old), but usually, only one\u00a0<u>AScope<\/u>\u00a0exists.<\/p>\n<p><strong>5)\u00a0<\/strong>Logs and OT use the same approach for the messages. This approach called\u00a0<u>structured logging<\/u>.<\/p>\n<p>This approach is very easy. There is only one important thing.\u00a0<strong>Your log message must\u00a0<u>not<\/u>\u00a0contain variable params.\u00a0<\/strong><\/p>\n<p><strong>6)\u00a0<\/strong>Real application has different layers.\u00a0<\/p>\n<p>PrimaryAdapter\u00a0\u2192\u00a0<strong>UseCase\u00a0\u2192 DomainService\u00a0\u2192 DAO<\/strong>\u00a0\u2192 Client (Secondary Adapter) \u2192 ExternalResource<\/p>\n<p>The central part of this chain usually builds by DI.<\/p>\n<p>For example:<\/p>\n<pre><code class=\"go\">type CService struct {}  func NewCService(..., logger logger.ILogger) *CService {}<\/code><\/pre>\n<p>And as you can see above Logger is an AScope object, because it should be created when we build our DI-container.<\/p>\n<p><strong>7)\u00a0Logger is a global object. We should not create a new logger on every request and store it into ctx.<\/strong><\/p>\n<p><strong>8)<\/strong>\u00a0<strong>We should pass context everywhere.<\/strong><\/p>\n<p>Context is the thing that contains our RScope. (function arguments contain request scope too &#8212; because they change between requests)<\/p>\n<p>OpenTracing widely uses context. (<a href=\"https:\/\/github.com\/opentracing\/opentracing-go#creating-a-span-given-an-existing-go-contextcontext\" rel=\"noopener noreferrer nofollow\">https:\/\/github.com\/opentracing\/opentracing-go#creating-a-span-given-an-existing-go-contextcontext<\/a>)<\/p>\n<p>So if you do not pass context &#8212; you do not have OpenTracing.<\/p>\n<p><strong>9)<\/strong>\u00a0Small piece of code.<\/p>\n<p><strong>\/pkg\/logger\/interface.go<\/strong><\/p>\n<pre><code class=\"go\">\/\/ ==== FIELD ==== type Field struct {    Key   string    Value interface{} }  func F(key string, value interface{}) Field {    return Field{Key: key, Value: value} }  \/\/ func FError - returns field based on error  \/\/ ==== LOGGER ==== type Logger interface {    WithFields(with ...Field) Logger     Debug(ctx context.Context, msg string, with ...Field)    Info(ctx context.Context, msg string, with ...Field)    Warn(ctx context.Context, msg string, with ...Field)    Error(ctx context.Context, msg string, with ...Field) }<\/code><\/pre>\n<p><strong>\/pkg\/logger\/context.go<\/strong><\/p>\n<pre><code class=\"go\">type key int  var loggerKey key  func NewContextWithFields(ctx context.Context, with ...Field) context.Context {        fields := make([]Field, 0)    fields = append(fields, fetchFieldsFromContext(ctx)...)    fields = append(fields, with...)     return context.WithValue(ctx, loggerKey, fields) }  func fetchFieldsFromContext(ctx context.Context) []Field {    if fields, ok := ctx.Value(loggerKey).([]Field); ok {       return fields    }     return nil }<\/code><\/pre>\n<p><strong>\/example.go<\/strong><\/p>\n<pre><code class=\"go\">func (uc *UseCase) SomeOperation(ctx context.Context, param uint64) {    span, ctx := opentracing.StartSpanFromContext(ctx, \"operation_name\")    defer span.Finish()      uc.logger.Info(ctx, \"Starting domain operation\", logger.F(\"param\", param))    if err := uc.domainSvc.SomeOperation(ctx, param); err != nil {       uc.logger.Error(ctx, \"Unable to perform domain operation\", logger.FError(err))       return    }    uc.logger.Info(ctx, \"Domain operation finished\") }<\/code><\/pre>\n<p>As you can see above Logger receives the context as the first argument.<\/p>\n<p>Why? &#8212; Because inside of Logger implementation we can define either we need to send this message to OpenTracing collector (if ctx contains span) or just to stdout. (backward compatibility)<\/p>\n<p><strong>10)<\/strong>\u00a0One more thing. We need to process our errors only one time. SO if we return an error we need\u00a0<u>only<\/u>\u00a0return it. We should not log it inside the same function.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/675010\/\"> https:\/\/habr.com\/ru\/articles\/675010\/<\/a><br \/><\/br><\/br><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-405189","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/405189","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=405189"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/405189\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=405189"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=405189"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=405189"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}