-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.json
176 lines (176 loc) · 65.9 KB
/
index.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
[
{
"uri": "https://go-masonry.github.io/api/",
"title": "APIs",
"tags": [],
"description": "",
"content": "Mortar\u0026rsquo;s goal is to let you easily create gRPC and REST web services.\nMortar supports:\n gRPC API: you need to define proto files and compile them, read more here. RESTful API: can be implemented in several ways: Using gRPC-Gateway, read more here.\n Generate reverse-proxy from protos or via yaml. You can also add custom routes to it. Custom route using grpc-gateway ServeMuxYou can\u0026rsquo;t access runtime.ServeMux unless you create it yourself.\nOnce you do, pass it during HTTP server build, using a dedicated SetCustomGRPCGatewayMux builder option.\nYou will also need to use your own builder instead of a default one.\n Register custom HTTP Handler/HandlerFunc(s) using a dedicated group.\n "
},
{
"uri": "https://go-masonry.github.io/clients/",
"title": "Clients",
"tags": [],
"description": "",
"content": "Mortar Clients are no different then the standard ones, in fact they are the same. Mortar only makes them conveniently configurable.\nBefore you continue, please make sure you are familiar with Dependency Injection and Uber-Fx Groups.\nMortar supports the following clients out-of-the-box.\n gRPC HTTP "
},
{
"uri": "https://go-masonry.github.io/clients/grpc/",
"title": "gRPC Client",
"tags": [],
"description": "",
"content": "gRPC Clients are usually generated by Protobuf compilers such as protoc or buf. The connection itself is generic and this is where Mortar enters.\n// GRPCClientConnectionWrapper is a convenience wrapper to support predefined dial Options // provided by GRPCClientConnectionBuilder type GRPCClientConnectionWrapper interface { // Context can be nil Dial(ctx context.Context, target string, extraOptions ...grpc.DialOption) (grpc.ClientConnInterface, error) } // GRPCClientConnectionBuilder is a convenience builder to gather []grpc.DialOption type GRPCClientConnectionBuilder interface { AddOptions(opts ...grpc.DialOption) GRPCClientConnectionBuilder Build() GRPCClientConnectionWrapper } GRPCClientConnectionWrapper explainedGRPCClientConnectionWrapper.Dial method is very similar to one defined in gRPC client.\nfunc DialContext(ctx context.Context, target string, opts ...DialOption) (conn *ClientConn, err error) However, the return value from gRPC method is *grpc.ClientConn which is a struct. This makes it difficult to mock if needed. Lately gRPC project added grpc.ClientConnInterface to help them in tests, but since it\u0026rsquo;s an Interface it was borrowed\u0026hellip;\n Mortar provides a Builder interface that allows you to add different Options before creating a connection. While you can create one yourself, Mortar comes with predefined Client Builders. You just need to make sure they\u0026rsquo;re registered/provided in Uber-Fx.\nHTTPClientBuildersFxOption is used for both gRPC and HTTP Clients.\n Once provided you should inject the client.GRPCClientConnectionBuilder.\nCustomizing gRPC connection While you should add all the gRPC Client Interceptors using a provided Uber-Fx group GRPCUnaryClientInterceptors, the reason you inject the client.GRPCClientConnectionBuilder is the ability to add custom options for a particular connection.\n Ignore TLS by adding grpc.WithInsecure() only when connecting to server X. Block until the underlying connection is up using grpc.WithBlock() when establishing a connection against service Y. More options can be found here. Example We will use our Demo -\u0026gt; SubWorkshop as an example.\n Register/Provide HTTP Clients dependencies.\nimport ( \u0026#34;github.com/go-masonry/mortar/providers\u0026#34; \u0026#34;go.uber.org/fx\u0026#34; ) func HttpClientFxOptions() fx.Option { return fx.Options( providers.HTTPClientBuildersFxOption(), // client builders for gRPC and HTTP ) } Inject builder.\ntype subWorkshopControllerDeps struct { fx.In GRPCClientBuilder client.GRPCClientConnectionBuilder } Create a connection.\nwrapper := s.deps.GRPCClientBuilder.Build() conn, err := wrapper.Dial(ctx, \u0026#34;localhost:5381\u0026#34;, grpc.WithInsecure()) if err != nil { return fmt.Errorf(\u0026#34;can\u0026#39;t connect to %s, %w\u0026#34;, \u0026#34;localhost:5381\u0026#34;, err) } Use this connection to create a gRPC client.\ngrpcClient := protopackage.NewServiceClient(conn) resp, err := grpcClient.DoSomething(ctx, \u0026amp;protopackage.ServiceRequest{}) if err != nil { return err } fmt.Printf(\u0026#34;Response: %s\u0026#34;, resp) "
},
{
"uri": "https://go-masonry.github.io/clients/http/",
"title": "HTTP Client",
"tags": [],
"description": "",
"content": "Mortar HTTP Client is no other than *http.Client.\nSimilar to gRPC Client Mortar makes http.Client very convenient to configure and adds Interceptors capabilities.\n// HTTPClientBuilder is a builder interface to build http.Client with interceptors type HTTPClientBuilder interface { AddInterceptors(...HTTPClientInterceptor) HTTPClientBuilder WithPreconfiguredClient(*http.Client) HTTPClientBuilder Build() *http.Client } // NewHTTPClientBuilder REST HTTP builder // // Useful when you want to create several *http.Client with different options type NewHTTPClientBuilder func() HTTPClientBuilder As you can see above, there is a builder and a function type alias NewHTTPClientBuilder. Using NewHTTPClientBuilder it\u0026rsquo;s possible to create different instances of http.Client which have a common set of Interceptors, but can be individually configured with additional ones. It\u0026rsquo;s very similar to what we have with gRPC.\n// HTTPClientBuilder creates an injectable http.Client builder that can be predefined with Interceptors // // This function returns a closure that will always create a new builder. That way every usage can add different // interceptors without influencing others func HTTPClientBuilder(deps httpClientBuilderDeps) clientInt.NewHTTPClientBuilder { return func() clientInt.HTTPClientBuilder { return client.HTTPClientBuilder().AddInterceptors(deps.Interceptors...) } } The following code snippet is defined in Mortar. HTTPClientBuilder function is already in use if you register HttpClientFxOptions.\n Now you know how it\u0026rsquo;s defined in Mortar, but how do you use it to create an http.Client ?\nExample For a complete working example look here.\n Register\nimport ( \u0026#34;github.com/go-masonry/mortar/providers\u0026#34; \u0026#34;go.uber.org/fx\u0026#34; ) func HttpClientFxOptions() fx.Option { return fx.Options( providers.HTTPClientBuildersFxOption(), // client builders for gRPC and HTTP ) } Inject\nimport \u0026#34;github.com/go-masonry/mortar/interfaces/http/client\u0026#34; ... type yourDeps struct { fx.In HTTPClientBuilder client.NewHTTPClientBuilder } Use\nAt this point it is possible to add additional Interceptors using deps.HTTPClientBuilder()... that will influence only this client instance.\n// Create a client instance var client *http.Client = deps.HTTPClientBuilder().Build() var httpReq *http.Request = makeHTTPRequest() response, err := w.client.Do(httpReq) "
},
{
"uri": "https://go-masonry.github.io/clients/interceptors/",
"title": "Interceptors",
"tags": [],
"description": "",
"content": "Interceptors are a powerful tool that enables you to log, monitor, mutate, redirect and sometimes even fail a request or response. The beauty of interceptors is, that to the user everything looks as a regular Client.\nWith Client Interceptors you can:\n Dump request and/or response to a Log Alter your requests before they sent and/or responses before they returned. Add Tracing Information Collect metrics about your remote calls. Mortar comes with some useful client Interceptors both for gRPC and HTTP, they are mentioned here.\nHTTP Client Interceptor Mortar introduces a way to add Interceptors to a default http.Client.\nIf we look at http.Client struct we will find a RoundTripper there.\n// Transport specifies the mechanism by which individual // HTTP requests are made. // If nil, DefaultTransport is used. type RoundTripper interface { // RoundTrip executes a single HTTP transaction, returning // a Response for the provided Request. // ... RoundTrip(*Request) (*Response, error) } Mortar defines HTTPHandler and HTTPClientInterceptor type aliases in mortar/interfaces/http/client/interfaces.go.\nThey mimic their gRPC counterparts.\n// HTTPHandler is just an alias to http.RoundTriper.RoundTrip function type HTTPHandler func(*http.Request) (*http.Response, error) // HTTPClientInterceptor is a user defined function that can alter a request before it\u0026#39;s sent // and/or alter a response before it\u0026#39;s returned to the caller type HTTPClientInterceptor func(*http.Request, HTTPHandler) (*http.Response, error) You can see that HTTPHandler signature is very similar to RoundTrip(*Request) (*Response, error)\u0026hellip;\nThat\u0026rsquo;s everything we need to enable HTTP Client interceptors.\nDump When we create an HTTP Request we use different Structs and Functions, but we don\u0026rsquo;t set Content-Length Header for example. Dumping Utilities allow you to see what is actually sent and what is actually received. Mortar comes with an HTTP Client Interceptor that dumps(logs) the actual request and response.\nfunc DumpRESTClientInterceptor(deps dumpHTTPDeps) client.HTTPClientInterceptor { return func(req *http.Request, handler client.HTTPHandler) (*http.Response, error) { reqBody, err := httputil.DumpRequestOut(req, true) deps.Logger.WithError(err).Debug(req.Context(), \u0026#34;Request:\\n%s\\n\u0026#34;, reqBody) res, err := handler(req) if err == nil { resBody, dumpErr := httputil.DumpResponse(res, true) deps.Logger.WithError(dumpErr).Debug(req.Context(), \u0026#34;Response:\\n%s\\n\u0026#34;, resBody) } return res, err } } GRPC Client Interceptor GRPC Interceptors are part of the gRPC package already. Besides mentioning it Mortar has nothing to add.\nRegistering Registering your Interceptors is very similar to everything else in Mortar, using Uber-Fx option. Once created you need to add them to their respectful group.\n HTTP Client Interceptors group is called restClientInterceptors and referenced by RESTClientInterceptors defined here.\n// TracerRESTClientInterceptorFxOption adds REST trace client interceptor to the graph func TracerRESTClientInterceptorFxOption() fx.Option { return fx.Provide( fx.Annotated{ Group: groups.RESTClientInterceptors, Target: trace.TracerRESTClientInterceptor, }) } GRPC Unary Client Interceptors group is called grpcUnaryClientInterceptors and referenced by GRPCUnaryClientInterceptors defined here.\n// TracerGRPCClientInterceptorFxOption adds grpc trace client interceptor to the graph func TracerGRPCClientInterceptorFxOption() fx.Option { return fx.Provide( fx.Annotated{ Group: groups.GRPCUnaryClientInterceptors, Target: trace.TracerGRPCClientInterceptor, }) } You can look here to see how the above two are used.\n"
},
{
"uri": "https://go-masonry.github.io/fx/",
"title": "Dependency Injection",
"tags": [],
"description": "",
"content": "If you are unfamiliar with the concept, please read about it.\nMortar is heavily based on this principle. There are different libraries to achieve that in Go.\nMortar uses Uber-FX and you\u0026rsquo;re strongly encouraged to read all about it.\nWell, actually you kinda have to.\n To summarize, this is what you need to understand about IoC frameworks:\n You are not the one creating the dependencies, Uber-FX does it for you. Just tell it how. Every dependency is a Singleton, hence that same instance is reused everywhere. Once your dependencies are defined as an interface, it\u0026rsquo;s really easy to swap it. Especially during tests. There is no magic, only implicits. "
},
{
"uri": "https://go-masonry.github.io/middleware/",
"title": "Middleware",
"tags": [],
"description": "",
"content": "It\u0026rsquo;s hard to define middleware, but in Mortar\u0026rsquo;s context you can think of it as proxy/interceptors. Some of them are bundled as part of the libraries Mortar uses, others defined by Mortar.\n The gRPC package already has a way to register different interceptors for client and server. Mortar already comes with some:\n Server interceptors: Logger UnaryServerInterceptor, making it easier to log gRPC request and response. Monitoring UnaryServerInterceptor, this one can create automatic Histogram/Timer metric for each gRPC method. Tracing UnaryServerInterceptor, injects or uses tracing information. Client interceptors: Tracing UnaryClientInterceptor, creates a client span and passes tracing information to the next hop. In-\u0026gt;Out UnaryClientInterceptor, this one can be used to copy specific headers from grpc.Incoming to grpc.Outgoing. HTTP interceptors:\n Server HTTP middleware. There are no http.Handler interceptors bundled with Mortar. It\u0026rsquo;s better to use the gRPC layer for that, but you can always add your own.\n Client. Mortar introduces a way to add interceptors to http.Client, read here to understand how.\n Tracing HTTPClientInterceptor, similar to the gRPC one, but for HTTP. Request/Response dump interceptor, for debugging purposes. **In Workshop Demo there is a special TEST Interceptor that returns a custom response. Other types of middleware Network interceptors are not the only ones you can use in Mortar. There are other types of middleware.\nThey rely on context.Context passed to most methods, read here about that.\n"
},
{
"uri": "https://go-masonry.github.io/auth/",
"title": "JWT Authentication",
"tags": [],
"description": "",
"content": "In case you decide to use JWT as your Auth* of choice, Mortar have a simple Token Extractor.\ntype TokenExtractor interface { // FromContext should try to extract a token from the Context using `ContextExtractor` \tFromContext(ctx context.Context) (Token, error) // FromString accepts a JWT token in form of a string \t//\txxxxx.yyyyy.zzzzz \tFromString(str string) (Token, error) } Mortar and gRPC-Gateway Usually you pass the JWT Token via the Authorization header\nAuthorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c Now, you are encouraged to use gRPC-Gateway, but in this case you don\u0026rsquo;t really have an access to HTTP Request Headers. Fear not, gRPC-Gateway and Mortar have your covered.\n By default gRPC-Gateway will transform this special Authorization header into grpcgateway-authorization and put it in the Context. Mortar have a DefaultJWTTokenExtractor that takes care of that. Example You only need to register Token Extractor by providing DefaultJWTTokenExtractor constructor to Uber-Fx.\nimport ( \u0026#34;github.com/go-masonry/mortar/constructors\u0026#34; \u0026#34;go.uber.org/fx\u0026#34; ) ... fx.Provide(constructors.DefaultJWTTokenExtractor) ... Once provided, you can inject TokenExtractor and use it\ntype authDeps struct { fx.In TokenExtractor jwt.TokenExtractor } ... func (impl *authImpl) CheckJWT(ctx context.Context) error { // we only log for error here token, err := impl.deps.TokenExtractor.FromContext(ctx) if err != nil { return fmt.Errorf(\u0026#34;no jwt token found, %w\u0026#34;,err) } m, err = token.Map() if err != nil { return fmt.Errorf(\u0026#34;failed to produce a map from authorization header, %w\u0026#34;, err) } ... ... return nil } Mortar service template have a simple working example. Look at app/validations/auth.go.\n"
},
{
"uri": "https://go-masonry.github.io/mortar/",
"title": "Mortar Design",
"tags": [],
"description": "",
"content": "Mortar comes with different design decisions, some of them good for you, and some are not. Remember you can always replace everything.\n Bricks Builder Pattern Configuration Map Listeners "
},
{
"uri": "https://go-masonry.github.io/",
"title": "",
"tags": [],
"description": "",
"content": "Mortar is a Go framework/library for building gRPC (and REST) web services.\nIt has out-of-the-box support for configuration, application metrics, logging, tracing, profiling, dependency injection and more.\nThe key features are:\n Dependency Injection using Uber-FX. Out-of-the-box defaults that work, however you can replace everything. Middleware concept is almost everywhere. Build-in Server/Client Interceptors both for gRPC/HTTP, you can bring your own. Telemetry - Metrics, Tracing and Logging all connected. Pimped gRPC and HTTP clients. Increases Developer Velocity DevOps friendly and Production ready. Builder pattern Profiling, Debug, Build Info, Configuration and more. Create a production ready service in 15min using this service template. If you want to better understand Mortar features, clone the Demo repository and follow the README there.\n "
},
{
"uri": "https://go-masonry.github.io/mortar/bricks/",
"title": "Bricks",
"tags": [],
"description": "",
"content": "When we build software, most of us always try to rely on something we or others have built before us. Since we don\u0026rsquo;t want to reinvent the wheel, again.\nGo\u0026rsquo;s standard library is an excellent example here. There are strings, time, http and many other build-in libraries that we use. While this example is great, it doesn\u0026rsquo;t scale to 3rd party libraries.\nLet\u0026rsquo;s look at Logger libraries for example, there are:\n Logrus Apex Zap Zerolog \u0026hellip; You are encouraged to have a look at each library, but I can assure you, all of their APIs are different from each other. Mortar after all is a library, and its purpose to be used in many projects. That is why we defined different interfaces.\nWe define a Brick as an implementation of an interface defined in Mortar using an external library Mortar Logger Implementation using Zerolog Mortar Config Implementation using Viper Mortar Tracing This is a special case, since open tracing is already an abstraction.\n Implementation using Jaeger \u0026hellip; To easily differentiate an actual library from its Brick wrapper, every Brick package starts with a b.\n bviper bzerolog \u0026hellip; As a rule, every Brick is based on Mortar itself, but not on any other Brick Meaning you can\u0026rsquo;t import bzerolog from within bjaeger, for example.\nCheck their go.mod files to make sure.\nThis rule is here to ensure that every Brick can be easily swapped.\n"
},
{
"uri": "https://go-masonry.github.io/mortar/builders/",
"title": "Builder Pattern",
"tags": [],
"description": "",
"content": "You\u0026rsquo;re probably used to functional options pattern. However, we found Builder pattern to be very useful, here we will explain why.\nMotivation \u0026ldquo;See\u0026rdquo; all the options without searching for them. Partial Builders: Override previously set values Library usage within the organization +---------------------+ +-------------------------------+ +-------------------------+ | Library Developer | +--------\u0026gt; | Platform/Infra/Ops Developer | +-------\u0026gt; | Integration Developer | +---------------------+ +-------------------------------+ +-------------------------+ Develops the library to be used Pre-configure the library specifically Set final values in different scenarios, expose to their organization. according to specific use case. different options. Library defaults Predefined defaults Override defaults \n Our Library Let\u0026rsquo;s pretend we are building a library. Our library will have a constructor:\nfunc NewLib(options ...Option) Library {} type Option func(*libConf) \n Configuration will be stored in libConf: type libConf struct { address string // other options omitted for clarity... } func Address(addr string) Option { return func(cfg *libConf) { cfg.address = addr } } \nThere can also be a different kind of option that will look similar to this, but the idea is the same What is it about then? The purpose of this pattern is to \u0026ldquo;introduce\u0026rdquo; or \u0026ldquo;unite\u0026rdquo; between a library developer and the platform/infra developer as shown in the above chart. We want to introduce predefined defaults that are specific to the organization/use case and are not just library specific. Let\u0026rsquo;s examine our Library again, we have to set an address since our library needs to connect to some server. We can look at this problem from difference perspectives:\nLibrary DeveloperThe library developer has no idea about your IP address, right? Hence the exposed Option to set the address. However, to find that option or any others you will need to look at the source code, right ?\n Infrastructure/Platform developer within your organizationAn Infrastructure/Platform developer has already setup this server and now needs to tell every \u0026ldquo;Integration developer\u0026rdquo; what that address is.\n Integration DeveloperEither knows or not about this server IP that was introduced by the Platform/Infrastructure team but still needs to have a way to override it, because this IP can change with time or there is a local server that is used during tests.\n Builder type LibBuilder interface { Address(addr string) LibBuilder // ... additional options omitted for clarity Build() Library } \n So far nothing new, you can even look at it as a set of options without the ability to extend and not break the API. And you\u0026rsquo;re right, however it\u0026rsquo;s not intended as a drop-in replacement for functional options.\nLinked list The Builder implementation is based on a linked list: +-------------------------------+ | | +----------------------------\u0026gt;+ Configuration | | | | | +-------+------------------+----+ | ^ ^ | | | +---------+-----------+ +---------------+-----+ +--+------------------+ | | | | | | +-----\u0026gt;+ Address Option +--------\u0026gt;+ Max Option +--------\u0026gt;+ Min Option | | | | | | | +---------------------+ +---------------------+ +---------------------+ \n Each Option is presented as a function on the builder interface and added a to a list of previous options. Each Builder function is an alias to a functional option\n Overriding predefined defaults Since it\u0026rsquo;s a list of options, we can add a new option to the end of a list that will actually override a previous one: +-------------------------------+ | | +----------------------------\u0026gt;+ Configuration | | | | | +-------+------------------+----+ | ^ ^ | | | +---------+-----------+ +---------------+-----+ +--+------------------+ +---------------------+ | | | | | | | | +-----\u0026gt;+ Address Option +--------\u0026gt;+ Max Option +--------\u0026gt;+ Min Option +--------\u0026gt;+ Address Option | | | | | | | | | +---------+-----------+ +---------------------+ +---------------------+ +-----------+---------+ ^ | | | +-----------------------------------------OVERRIDES-----------------------------------------------+ + + | | +---------------------------------------Predefined defaults---------------------------------+ \n Implementation Finally, let\u0026rsquo;s just build it: import ( \u0026#34;fmt\u0026#34; \u0026#34;container/list\u0026#34; ) type libConf struct { address string } type Library string type LibBuilder interface { Address(addr string) LibBuilder // ... additional options omitted for clarity \tBuild() Library } type libBuilder struct { ll *list.List } func Builder() LibBuilder { return \u0026amp;libBuilder{ ll: list.New(), } } func (b *libBuilder) Address(addr string) LibBuilder { b.ll.PushBack(func(cfg *libConf) { fmt.Printf(\u0026#34;using %s as an address\\n\u0026#34;, addr) // for debug \tcfg.address = addr }) return b } func (b *libBuilder) Build() Library { var cfg = new(libConf) // empty conf \t// Iterate on the linked list \tfor e := b.ll.Front(); e != nil; e = e.Next() { f := e.Value.(func(*libConf)) // extract and cast \tf(cfg) } // at this point cfg will be populated \treturn Library(fmt.Sprintf(\u0026#34;We are going to call [%s] address\u0026#34;, cfg.address)) // or something similar } \n Once we have our builder, we will use it as follows:\nfunc makeLibrary(partialBuilder LibBuilder) Library { // Here we have passed previously defined builder, one that has some defaults already in it. // Now we can either use it as is or override it. builder := partialBuilder.Address(\u0026#34;5678\u0026#34;) return builder.Build() } func main() { var builder = Builder().Address(\u0026#34;1234\u0026#34;) // predefined builder \tlib:=makeLibrary(builder) fmt.Println(lib) } \n Now, if you run it the output will be: using 1234 as an address using 5678 as an address We are going to call [5678] address \n "
},
{
"uri": "https://go-masonry.github.io/mortar/config/",
"title": "Configuration Map",
"tags": [],
"description": "",
"content": "It is good practice to use constants in your code instead of magic numbers, and it\u0026rsquo;s even better to set them outside your code, either by providing a config file or reading from an environment variable. Mortar has a Config interface that is used everywhere to read external configurations. While Mortar can be configured explicitly, and that gives you total control over it, it is much comfortable to use its defaults. To read them, Mortar expects a dedicated configuration key called mortar:\nmortar:name:\u0026#34;tutorial\u0026#34;server:grpc:port:5380rest:external:port:5381internal:port:5382...\n Every project should have a configuration file, and yours is no exception. You can put all your external configuration values in it (or them).\nThe concept is simple, you use the Get function that accepts a key. A key is actually a path within the configuration map. Looking at the above example, to access the gRPC server port, you should use the following key:\nmortar.server.grpc.port\nThe default delimiter is . but if needed it can be changed, with a proper PR\n Once you Get a value with a provided key you can:\n Check if it was set value.IsSet() bool. Cast it to a type: Bool() bool Int() int StringMapStringSlice() map[string][]string \u0026hellip; Environment variables While it depends on the implementation, you should assume that if there is an environment variable with a matching name, its value is going to be used first.\nMatching environment variable names As mentioned previously, the default delimiter is .. However, when naming environment variables you can\u0026rsquo;t use .. It is expected the that chosen implementation will allow you to configure a delimiter replacer. If you choose to use viper using the brick wrapper, by default there is a replacer that will replace the _ delimiter to . used in our code.\nThis is better explained with an example. Look at the map below:\n mortar:server:grpc:port:5380\n Let\u0026rsquo;s say you want to change port value from 5380 to 7777. You can change the file itself. However, you can also override it. Viper allows you to override configuration values with a matching environment variable. In our case:\n export MORTAR_SERVER_GRPC_PORT=\u0026#34;7777\u0026#34; \n When you want to read the gRPC server port value in your code, you should use this as a key:\nmortar.server.grpc.port\nViper will look for an environment variable by replacing _ with . case-insensitive first and return its value if set.\nMortar Keys Mortar expects different keys in its configuration map to enable or configure different abilities.\nWill probably be RefactoredIn this documentation we try to show what the configuration map should look like and expose all the keys.\n Config format While in this example we showed you config.yml in YAML format, you can choose whatever works for you, as long as the provided Config implementation knows how to read it and will abstract every key to be queried in this form:\nroot.child.childOfChild\nMortar expects this config.yaml template # Root key of everything related to mortar configurationmortar:# Application/Project name# Type: stringname:\u0026#34;Application Name\u0026#34;# Web server related configurationserver:grpc:# gRPC API External port# Type: intport:5380rest:# RESTful API External port# Type: intexternal:port:5381# RESTful API Internal port# Type: intinternal:port:5382# Default Logger related configurationlogger:# Set the default log level for mortar logger# Possible values:#\ttrace, debug, info, warn, error# Type: stringlevel:debugstatic:# enables/disables adding a git commit SHA in every log entry# Type: boolgit:true# enables/disables adding a hostname in every log entry# Type: boolhost:false# enables/disables adding an application/project name in every log entry# Type: boolname:false# Metrics/Monitoring related configurationmonitor:# sets the namespace/prefix of every metric. Depends on the Metrics implementation# Type: stringprefix:\u0026#34;awesome\u0026#34;# allows to include static labels/tags to every published metric# Type: map[string]stringtags:tag1:value1tag2:value2tag3:value3# Bundled handlers configurationhandlers:config:# defines a list of keywords that once contained within the configuration key will obfuscate the value# Type: []stringobfuscate:- \u0026#34;pass\u0026#34;- \u0026#34;auth\u0026#34;- \u0026#34;secret\u0026#34;- \u0026#34;login\u0026#34;- \u0026#34;user\u0026#34;# Interceptors/Extractors configurationmiddleware:# set the default log level of all the bundled middleware that writes to log# Possible values:# trace, debug, info, warn, error# Type: stringlogLevel:\u0026#34;trace\u0026#34;# list of headers to be extracted from Incoming gRPC and added to every log entry# Type: []stringlogHeaders:- \u0026#34;x-forwarded-for\u0026#34;- \u0026#34;special-header\u0026#34;trace:http:client:# include HTTP client request to trace info ?# Type: boolrequest:true# include HTTP client response to trace info ?# Type: boolresponse:truegrpc:client:# include gRPC client request to trace info ?# Type: boolrequest:true# include gRPC client response to trace info ?# Type: boolresponse:trueserver:# include incoming gRPC request to trace info ?# Type: boolrequest:true# include a gRPC response of incoming request to trace info ?response:truecopy:# list of header prefixes to copy/forward from Incoming gRPC context to outgoing Request context/headers# Type: []stringheaders:- \u0026#34;authorization\u0026#34;"
},
{
"uri": "https://go-masonry.github.io/middleware/context/",
"title": "Context",
"tags": [],
"description": "",
"content": "If you are not familiar with context.Context, please read this and that first.\nFrom the context documentation package:\nIncoming requests to a server should create a Context, and outgoing calls to servers should accept a Context. Since we are building a gRPC web service, it\u0026rsquo;s part of the design. Everything gRPC related already has a context.Context as the first argument.\ngRPC Client:\nfunc (cc *ClientConn) Invoke(ctx context.Context, method string, args, reply interface{}, opts ...CallOption) error Interceptors:\ntype UnaryClientInterceptor func(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, invoker UnaryInvoker, opts ...CallOption) error type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error) Also, all the generated server endpoints have context.Context as the first argument.\n Storage One of the \u0026ldquo;drawbacks\u0026rdquo; in Go is a lack of storage per go routine. You can \u0026ldquo;solve\u0026rdquo; this drawback by:\n Having a sync map[goroutine-id]storage, but it\u0026rsquo;s probably not a practical idea. Propagate context.Context as the first parameter for every public function. Actually, the second option is largely encouraged and has become a kind of a standard.\n Tracing uses context to store a traceId, spanId and other information. grpc-gateway reverse proxy injects selected http.Request headers into the context. In Mortar, most interfaces have a way to leverage on context.Context, and depending on the package there are different ContextExtractor definitions.\nLet\u0026rsquo;s look at Logging as an example.\nLogging Example All Logger methods have a context.Context as their first argument:\nDebug(ctx context.Context, format string, args ...interface{}) Info(ctx context.Context, format string, args ...interface{}) Meaning that every log entry has a context\u0026hellip;\nInternally, just before we send the log to the implementing library, we iterate on every provided ContextExtractor and add all the extracted key-\u0026gt;value pairs to the log entry.\nfunc (c *contextAwareLogEntry) enrich(ctx context.Context) (logger log.Fields) { defer func() { if r := recover(); r != nil { c.innerLogger.WithField(\u0026#34;__panic__\u0026#34;, r).Error(ctx, \u0026#34;one of the context extractors panicked\u0026#34;) logger = c.innerLogger } }() logger = c.innerLogger for _, extractor := range c.contextExtractors { for k, v := range extractor(ctx) { logger = logger.WithField(k, v) } } return } One of such ContextExtractor is provided by the bjaeger wrapper.\nfunc extractFromSpanContext(ctx jaeger.SpanContext) map[string]interface{} { var output = make(map[string]interface{}, 4) output[SpanIDKey] = ctx.SpanID().String() output[ParentSpanIDKey] = ctx.ParentID().String() output[SampledKey] = ctx.IsSampled() if traceID := ctx.TraceID(); traceID.IsValid() { output[TraceIDKey] = traceID.String() } return output } If there is a Trace Span within the Context, we will include its information in the log entry.\nFor additional information about Logging, Context and Telemetry read here.\n"
},
{
"uri": "https://go-masonry.github.io/fx/pattern/",
"title": "Dependency Pattern",
"tags": [],
"description": "",
"content": "We are using this pattern when creating a new Uber-FX dependency, it makes it easy adding new external dependencies later when your application evolves. To explain this better, we will do this with a Notifier example.\nPreparation Define our Notifier interface:\ntype Notifier interface{ Alert(ctx context.Context, msg string) } Create a dependency \u0026ldquo;container\u0026rdquo; for all future dependencies of the Notifier implementation and embed fx.In into it. This will mark it and will tell Uber-FX to inject all the requested dependencies:\n// It\u0026#39;s not public as you can see type notifierDeps struct { fx.In } Create an implementation struct:\n// Notice again that\u0026#39;s not public type notifier struct{ deps notifierDeps } Create a Constructor function that will return a Notifier implementation as a type:\nfunc CreateNotifier(deps notifierDeps) (Notifier,error) { return \u0026amp;notifier(deps:deps), nil } Finally, implement it:\nfunc (n *notifier) Alert(ctx context.Context, msg string) { // alert someone } Usage Now, suppose you want to log every time you alert someone. All you need to do is:\n Add a log.Logger dependency to the notifierDeps struct:\ntype notifierDeps struct { fx.In Logger log.Logger } Use it:\nfunc (n *notifier) Alert(ctx context.Context, msg string) { n.deps.Logger.WithField(\u0026#34;msg\u0026#34;, msg).Debug(ctx, \u0026#34;alerting\u0026#34;) // alert someone } Tests You are happily using Notifier in your application, but what about tests? You know, to test logic that has Notifier as a dependency. Notifier will probably call an external service, which is not available during tests. One way to do it, is to use gomock.\n You can add a comment above the Notifier interface:\n//go:generate mockgen -source=notifier.go -destination=mock/notifier_mock.go type Notifier interface{ Alert(ctx context.Context, msg string) } Execute go generate ./... and it will generate all the mocks in your application.\n Use the generated mock in your tests. Remember not to call a real Notifier Constructor Mortar includes mocks of all of its interfaces in their respective directories, for example a Logger mock.\nYou can find several test examples here.\n"
},
{
"uri": "https://go-masonry.github.io/fx/groups/",
"title": "Groups",
"tags": [],
"description": "",
"content": "Uber-FX group is a feature that allows you to consume and produce multiple values of the same type. This makes it easier to influence/configure different instances.\nMortar has different groups, but we will focus on one of them here.\nInternal HTTP Handlers// InternalHTTPHandlers - Internal Http Handlers group. Mortar comes with several internal handlers, you can add yours. InternalHTTPHandlers = partial.FxGroupInternalHTTPHandlers //InternalHTTPHandlerFunctions - Internal Http Handler Functions group. Similar toInternalHttpHandlers but for functions InternalHTTPHandlerFunctions = partial.FxGroupInternalHTTPHandlerFunctions \n Build Information Similar to everything else in Uber-FX, to register a new instance into a fx.Group you need to create a Constructor.\nWe will look at one of the internal handlers: Build Information.\nBuild InformationIn this example we want to register a new internal [\u0026quot;/\u0026lt;path\u0026gt;/\u0026lt;pattern\u0026gt;\u0026quot; -\u0026gt; http.HandlerFunc] pair.\n Let\u0026rsquo;s start by defining a constructor that will return http.HandlerFunc:\nfunc (s *selfHandlerDeps) BuildInfo() http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { information := mortar.GetBuildInformation(true) // using true here will insert a default value if there is none if err := json.NewEncoder(w).Encode(information); err != nil { w.WriteHeader(http.StatusInternalServerError) s.Logger.WithError(err).Warn(nil, \u0026#34;failed to serve build info\u0026#34;) } } } Create an HTTPHandlerPatternPair. This is the part where you define on what path this handler will be served:\n// SelfHandlers this service information handlers func SelfHandlers(deps selfHandlerDeps) []partial.HTTPHandlerPatternPair { return []partial.HTTPHandlerPatternPair{ {Pattern: \u0026#34;/self/build\u0026#34;, Handler: deps.BuildInfo()}, ... } } Tell Uber-FX that we want this instance of HTTPHandlerPatternPair added to the groups.InternalHTTPHandlerFunctions group.\nfunc InternalSelfHandlersFxOption() fx.Option { return fx.Provide( fx.Annotated{ Group: groups.InternalHTTPHandlers + \u0026#34;,flatten\u0026#34;, Target: handlers.SelfHandlers, }) } Lastly, in your application you need to provide the above InternalSelfHandlersFxOption() option to Uber-FX graph, here you can see how it\u0026rsquo;s done in the demo.\nreturn fx.New( ... InternalSelfHandlersFxOption(), ... ) "
},
{
"uri": "https://go-masonry.github.io/api/grpc/",
"title": "gRPC",
"tags": [],
"description": "",
"content": "Mortar is mostly about gRPC (and REST) web services. We will explain how you should register multiple gRPC/REST APIs. To make it easier, we will use mortar-demo in our example.\nProtobuf Before you implement any gRPC service related code, you first need to define the API by writing proto files. Once that\u0026rsquo;s done, you will call protoc and generate your \u0026lt;name\u0026gt;_grpc.pb.go, \u0026lt;name\u0026gt;.pb.go and \u0026lt;name\u0026gt;.pb.gw.go files.\nWe will be using the Workshop API example.\nservice Workshop { rpc AcceptCar(Car) returns (google.protobuf.Empty); rpc PaintCar(PaintCarRequest) returns (google.protobuf.Empty); rpc RetrieveCar(RetrieveCarRequest) returns (Car); rpc CarPainted(PaintFinishedRequest) returns (google.protobuf.Empty);} Implementing Once we have our gRPC server interfaces generated, we need to implement them. In our case, we need to implement this generated interface.\n// WorkshopServer is the server API for Workshop service. // All implementations must embed UnimplementedWorkshopServer // for forward compatibility type WorkshopServer interface { AcceptCar(context.Context, *Car) (*empty.Empty, error) PaintCar(context.Context, *PaintCarRequest) (*empty.Empty, error) RetrieveCar(context.Context, *RetrieveCarRequest) (*Car, error) CarPainted(context.Context, *PaintFinishedRequest) (*empty.Empty, error) mustEmbedUnimplementedWorkshopServer() } You can see how it\u0026rsquo;s done in app/services/workshop.go. Here is one of the methods:\n... func (w *workshopImpl) AcceptCar(ctx context.Context, car *workshop.Car) (*empty.Empty, error) { if err := w.deps.Validations.AcceptCar(ctx, car); err != nil { return nil, err } w.deps.Logger.WithField(\u0026#34;car\u0026#34;, car).Debug(ctx, \u0026#34;accepting car\u0026#34;) return w.deps.Controller.AcceptCar(ctx, car) } ... Registering Once we have the implementation covered, we need to register it. There are several steps you need to cover:\n Create a function that will return GRPCServerAPI. ImportantIn this function you must register the gRPC API implementation on the provided grpc.Server\n func workshopGRPCServiceAPIs(deps workshopServiceDeps) serverInt.GRPCServerAPI { return func(srv *grpc.Server) { workshop.RegisterWorkshopServer(srv, deps.Workshop) // Any additional gRPC Implementations should be called here } } You can look here to understand how it\u0026rsquo;s done in our workshop example.\n Next, add it to the groups.GRPCServerAPIs group as shown here: To better understand Mortar groups read here\n return fx.Options( // GRPC Service APIs registration fx.Provide(fx.Annotated{ Group: groups.GRPCServerAPIs, Target: workshopGRPCServiceAPIs, }), ... This way you can register multiple gRPC API implementations, and they will all be registered in one place.\n Now we need to add this option to the Uber-FX graph, as shown here:\nreturn fx.New( ... mortar.WorkshopAPIsAndOtherDependenciesFxOption(), ... ) "
},
{
"uri": "https://go-masonry.github.io/api/grpc-gw/",
"title": "gRPC-Gateway",
"tags": [],
"description": "",
"content": "Mortar comes with a gRPC-Gateway, which is a reverse-proxy that translates a RESTful HTTP API into gRPC. We will show how you should register its handlers, after you generate them from the proto files.\nRegister grpc-gateway Handlers Before reading this part, get yourself familiar with the gRPC API counterpart.\n If you\u0026rsquo;ve read the gRPC part, you simply need to add one function to Uber-FX graph. This function should return a slice of GRPCGatewayGeneratedHandlers.\n Register your grpc-gateway generated Handlers: ImportantEach handler function must register itself on the provided runtime.ServeMux\n func workshopGRPCGatewayHandlers() []serverInt.GRPCGatewayGeneratedHandlers { return []serverInt.GRPCGatewayGeneratedHandlers{ // Register workshop REST API func(mux *runtime.ServeMux, endpoint string) error { return workshop.RegisterWorkshopHandlerFromEndpoint(context.Background(), mux, endpoint, []grpc.DialOption{grpc.WithInsecure()}) }, // Any additional gRPC gateway registrations should be called here } } Now tell Uber-FX about it and make sure it\u0026rsquo;s added to the GRPCGatewayGeneratedHandlers group.\nHowever, in this case our function doesn\u0026rsquo;t return a single value, but an array of them. By default Uber-FX will treat it as an Array of Arrays [][]GRPCGatewayGeneratedHandlers. Hence we need to flatten our value.\n // GRPC Gateway Generated Handlers registration fx.Provide(fx.Annotated{ // \u0026#34;flatten\u0026#34; does this [][]serverInt.GRPCGatewayGeneratedHandlers -\u0026gt; []serverInt.GRPCGatewayGeneratedHandlers Group: groups.GRPCGatewayGeneratedHandlers + \u0026#34;,flatten\u0026#34;, Target: workshopGRPCGatewayHandlers, }) Finally, you need to add the above fx.Option to Uber-FX graph, as shown here.\n REST Enabled You now have an RESTful reverse-proxy that automatically translates REST API calls to their gRPC counterparts. Following this guide, your REST API will be exposed on 5381 port.\nPOST /v1/workshop/cars HTTP/1.1 Accept: application/json, */*;q=0.5 Accept-Encoding: gzip, deflate Connection: keep-alive Content-Length: 88 Content-Type: application/json Host: localhost:5381 { \u0026#34;body_style\u0026#34;: \u0026#34;HATCHBACK\u0026#34;, \u0026#34;color\u0026#34;: \u0026#34;blue\u0026#34;, \u0026#34;number\u0026#34;: \u0026#34;12345679\u0026#34;, \u0026#34;owner\u0026#34;: \u0026#34;me myself\u0026#34; } HTTP/1.1 200 OK Content-Length: 2 Content-Type: application/json Date: Thu, 21 Jan 2021 11:54:47 GMT Grpc-Metadata-Content-Type: application/grpc {} As you can see above grpc-gateway did a great job.\n"
},
{
"uri": "https://go-masonry.github.io/api/handlers/",
"title": "HTTP Handler/Func",
"tags": [],
"description": "",
"content": "Although it\u0026rsquo;s very convenient to use gRPC + gRPC-Gateway to serve RESTful APIs, you sometime want to use prebuilt HTTP Handler/HandlerFunc(s) or similar.\nMortar provides 2 fx.Group(s) for that: ExternalHTTPHandlers and ExternalHTTPHandlerFunctions, one for http.Handler and the other for http.HandlerFunc.\nTo better understand Mortar groups, read here\n Registering http.Handler To register a new http.Handler, you need to define how it\u0026rsquo;s going to be served == Pattern:\n Create a HTTPHandlerPatternPair:\nvar customHandler http.Handler ... func createHandlerPair() types.HTTPHandlerPatternPair { return types.HTTPHandlerPatternPair{ Pattern:\u0026#34;/your/pattern\u0026#34;, Handler: customHandler, } } Add this pair to the ExternalHTTPHandlers group:\nvar customHandlerOption = fx.Provide( fx.Annotated{ Group: groups.ExternalHTTPHandlers, Target: createHandlerPair, // function pointer, will be called by Uber-FX } ) All that\u0026rsquo;s left is to register it into the Uber-FX graph:\nreturn fx.New( ... customHandlerOption, ... ) Registering http.HandlerFunc Registering http.HandlerFunc is very much similar to registering http.Handler.\nActually, you need to follow the exact same guide and replace Handler with HandlerFunc:\nHandler var customHandler http.Handler ... func createHandlerPair() types.HTTPHandlerPatternPair { return types.HTTPHandlerPatternPair{ Pattern:\u0026#34;/your/pattern\u0026#34;, Handler: customHandler, } } var customHandlerOption = fx.Provide( fx.Annotated{ Group: groups.ExternalHTTPHandlers, Target: createHandlerPair, // function pointer, will be called by Uber-FX } ) return fx.New( ... customHandlerOption, ... ) HandlerFunc var customHandlerFunc http.HandlerFunc ... func createHandlerFuncPair() types.HTTPHandlerFuncPatternPair { return types.HTTPHandlerFuncPatternPair{ Pattern:\u0026#34;/your/pattern\u0026#34;, Handler: customHandlerFunc, } } var customHandlerFuncOption = fx.Provide( fx.Annotated{ Group: groups.ExternalHTTPHandlerFunctions, Target: createHandlerFuncPair, // function pointer, will be called by Uber-FX } ) return fx.New( ... customHandlerFuncOption, ... ) "
},
{
"uri": "https://go-masonry.github.io/mortar/listeners/",
"title": "Listeners",
"tags": [],
"description": "",
"content": "We assume that you, like us, don\u0026rsquo;t expose your services to the world without setting up at least a Load Balancer before it/them. Historically, when we started building webservices, our infrastructure was expecting a single port to forward all the traffic to it from the LoadBalancer. When we wanted to use gRPC API, as well as REST, we still had to expose everything under one port. Fortunately, we weren\u0026rsquo;t the first, and we used this excellent cmux library to solve that problem.\nNow, our services were hosted on AWS using ELB. Back then, ELB didn\u0026rsquo;t support HTTP/2. However, from Nov 2020 AWS have gRPC support\u0026hellip;\ngRPC is based on HTTP/2 and that presented some headache\u0026hellip; Long story short, we switched to Envoy and made the following changes:\nOur services now have had 3 listeners and 3 ports respectively:\n gRPC External REST, which is a reverse-proxy to gRPC using grpc-gateway. Internal REST which exposes different useful information about the service. While the first 2 were exposed to the Internet, the third one (Internal) wasn\u0026rsquo;t.\nMortar Mortar is designed with that in mind. Essentially, you create 3 web services with 3 ports they listen on. If that setup can\u0026rsquo;t work for you, you can still can do everything with 1 listener and 1 port.\nLet\u0026rsquo;s look at some benefits this approach has.\nExternal gRPC and REST Basically, you treat gRPC and External REST as one (but with 2 different ports). If your Load Balancer doesn\u0026rsquo;t support HTTP/2, it sure does support HTTP/1. It also can probably give you some benefits if you use an HTTP listener when routing traffic to the External REST port.\nInternal REST When your services are deployed in some cloud, you need different ways to \u0026ldquo;look\u0026rdquo; into them:\n What is their version or even better, Git commit. Configuration and environment values. Mortar has a simple way to obfuscate passwords, secret, tokens, etc.\n Sometimes you even want to profile your services by using provided handlers: Runtime Stats (Memory, CPU, \u0026hellip;).\n Flags provided when your service was started:\nservice config \u0026lt;path to file\u0026gt; --answer-to-everything=42 Or Debug them.\n Using Internal Handlers and their dedicated listener and port, you can set up a simple but very effective traffic rule, by allowing only office/vpn traffic to have access to it.\nOne Listener, One Port While multi-listener/port approach is great (well, at least for us), sometimes it\u0026rsquo;s not possible. You can still achieve everything using 1 listener and 1 port. Look at the test example here.\n"
},
{
"uri": "https://go-masonry.github.io/middleware/telemetry/logging/",
"title": "Logging",
"tags": [],
"description": "",
"content": "Logging is extremely important, that is why the Go standard library has one. However, there are better alternatives, and it\u0026rsquo;s up to you to choose the one you want. You will need to wrap it to implement Mortar Logger Interface and you\u0026rsquo;re good to go. We\u0026rsquo;ve already done that for zerolog.\nOne of the goals for Mortar Logger Interface was for it to be flexible, but most importantly it had to include context.Context, you\u0026rsquo;ll see later why. Before we go into the details let us start with some usage examples:\n Simple logging similar to fmt.Printf:\nlogger.Debug(ctx, \u0026#34;there were %d different attributes in the request\u0026#34;, 5) Structured style logging:\n// Add error, if err != nil it will be shown in the log logger.WithError(err).Debug(ctx, \u0026#34;calculation finished\u0026#34;) // Add Fields, error logger. WithError(err). WithField(\u0026#34;method\u0026#34;, req.Method). WithField(\u0026#34;status\u0026#34;, req.StatusCode). Debug(ctx, \u0026#34;request processed\u0026#34;) Output depends on the implementation, but most of the libraries will output logs in JSON/Console format.\nContext In our opinion you should always have context.Context as the first parameter for every public function you define and Logger is not an exception. You can read all the reasons for that here.\nSince every Logger function has a context.Context, we can extract different information from it (if configured) and ADD it to the log entry. When you setup a Logger instance to be used everywhere in your application, you can provide different ContextExtractors and they will enrich your log entry. So what is this mysterious ContextExtractor?\nContextExtractor\ntype ContextExtractor func(ctx context.Context) map[string]interface{} It\u0026rsquo;s a function that accepts a context.Context (which is a map) and outputs a JSON style map. Each key in the map will be added to the log entry.\nThis opens a lot of different possibilities:\n Add static fields such as [Host, Application Name/Version, etc].\n Add Tracing information.\n If configured properly through your entire infrastructure, this feature allows you to aggregate ALL the logsrelated to a single request across ALL services. Add dynamic fields from a request, for example X-Forwarded-For\n Registering a ContextExtractor Since Mortar is built with Uber-FX, it uses the fx.Group feature to register different ContextExtractors regardless of where they are defined. Here you can see what are Mortar Logger\u0026rsquo;s dependencies:\ntype loggerDeps struct { fx.In Config cfg.Config LoggerBuilder logInt.Builder `optional:\u0026#34;true\u0026#34;` ContextExtractors []logInt.ContextExtractor `group:\u0026#34;loggerContextExtractors\u0026#34;` } Look at the Group Identifier group:\u0026quot;loggerContextExtractors\u0026quot; which also has a group alias for reference purposes.\n\u0026ldquo;loggerContextExtractors\u0026rdquo;\n// LoggerContextExtractors - Default Logger Context extractors group. Add custom extractors to enrich your logs from context.Context LoggerContextExtractors = constructors.FxGroupLoggerContextExtractors Every ContextExtractor known to Uber-FX which is labeled with loggerContextExtractors will find its way there and will be registered.\nExample: showing how an FX Option is defined in external library Here is an example from a Jaeger package:\n// TraceInfoContextExtractorFxOption is a preconfigured fx.Option that will allow adding trace info to log entry func TraceInfoContextExtractorFxOption() fx.Option { return fx.Provide( fx.Annotated{ Group: groups.LoggerContextExtractors, // \u0026lt;\u0026lt;-- Group Name Target: func() log.ContextExtractor { return TraceInfoExtractorFromContext }, }, ) } Demo application shows how it\u0026rsquo;s registered app/mortar/logger.go:\nfunc LoggerFxOption() fx.Option { return fx.Options( ... bjaeger.TraceInfoContextExtractorFxOption(), ) } main.go in Demo application, starting it all:\nfunc createApplication(configFilePath string, additionalFiles []string) *fx.App { ... mortar.LoggerFxOption(), // Logger mortar.TracerFxOption(), // Jaeger tracing ... ) } Once everything is configured and provided you should see Tracing information in every log, regardless if it\u0026rsquo;s sampled or not.\n6:48PM DBG app/services/workshop.go:48 \u0026gt; accepting car app=workshop car={\u0026#34;body_style\u0026#34;:2,\u0026#34;color\u0026#34;:\u0026#34;blue\u0026#34;,\u0026#34;number\u0026#34;:\u0026#34;12345679\u0026#34;,\u0026#34;owner\u0026#34;:\u0026#34;me myself\u0026#34;} git=9f00a9c host=Tals-Mac-mini.lan parentSpanId=0 sampled=true spanId=4b71de0edc5b6d18 traceId=4b71de0edc5b6d18 version=v1.2.3 6:48PM DBG app/controllers/workshop.go:50 \u0026gt; car accepted app=workshop git=9f00a9c host=Tals-Mac-mini.lan parentSpanId=0 sampled=true spanId=4b71de0edc5b6d18 traceId=4b71de0edc5b6d18 version=v1.2.3 As you see above there are 2 logs, one is called from app/services/... the other from app/controllers/....\nBoth of these logs have the same traceId=4b71de0edc5b6d18. If you\u0026rsquo;ll forward this tracing information to another remote service you will see the same traceId=4b71de0edc5b6d18 in that service logs as well.\nNow you can aggregate them in your Logging solution and you will see all the logs related to that traceId from different services.\n"
},
{
"uri": "https://go-masonry.github.io/middleware/telemetry/monitoring/",
"title": "Monitoring",
"tags": [],
"description": "",
"content": "Monitoring helps us understand different insights from our Application.\nSimilar to Logging, Mortar Monitoring is all about the Interfaces. Implementations can be different\n Prometheus Datadog StatsD type Metrics interface { // Counter creates or loads a counter with possible predefined tags Counter(name, desc string) TagsAwareCounter // Gauge creates or loads a gauge with possible predefined tags Gauge(name, desc string) TagsAwareGauge // Histogram creates or loads a histogram with possible predefined tags Histogram(name, desc string, buckets Buckets) TagsAwareHistogram // Timer creates or loads a timer with possible predefined tags Timer(name, desc string) TagsAwareTimer // WithTags sets custom tags to be included if possible in every Metric WithTags(tags Tags) Metrics } When you use an Instance of this interface, Mortar uses an internal cache to remember every metric you create (with Labels/Tags).\nMetric name and labels/tags are used to create a Unique metric ID. Their order is insignificant. Usage Inject, create counter, increment.\nimport \u0026#34;github.com/go-masonry/mortar/interfaces/monitor\u0026#34; type serviceStruct struct { fx.In Metrics monitor.Metrics `optional:\u0026#34;true\u0026#34;` } func (s *serviceStruct) Do(ctx context.Context, req *proto.Request) { if s.Metrics != nil { counter := s.Metrics.Counter(\u0026#34;do_counter\u0026#34;,\u0026#34;Count number of time Do called\u0026#34;) counter.Inc() } } Metrics variable was injected with an optional:\u0026quot;true\u0026quot; tag. This tells Uber-Fx to ignore it if it wasn\u0026rsquo;t provided, leaving it nil.\nIn the example above we created a counter do_counter, it\u0026rsquo;s a Singleton. Next time when this code will be called or you will create a counter named do_counter somewhere else, Mortar will return the same instance.\nTags/Labels You can also add labels/tags to every metric. This is what you need to remember.\n You need to Declare Tags with their Default Values, before you create a Metric.\ncounter := w.deps.Metrics.WithTags(monitor.Tags{ \u0026#34;color\u0026#34;: \u0026#34;blue\u0026#34;, // default static value \u0026#34;success\u0026#34;: fmt.Sprintf(\u0026#34;%t\u0026#34;, err == nil), // the value will change every time this called. BUT not the metric name nor tags/labels keys. }).Counter(\u0026#34;paint_desired_color_counter\u0026#34;, \u0026#34;New paint color for car\u0026#34;) counter.Add(1) In this case Mortar will create (if not exists) a Counter identified internally by paint_desired_color_counter_color_success.\n You shouldn\u0026rsquo;t dynamically add Tags to a metric after it was created.\nThis feature depends on the implementation, Prometheus disallows // Counter was created without explicit tags/labels counter:=w.deps.Metrics.Counter(\u0026#34;accept_car_counter\u0026#34;, \u0026#34;Accepting a new car\u0026#34;) // Don\u0026#39;t try to add new Tags to a predefined Metric counter.WithTags(monitor.Tags{ \u0026#34;color\u0026#34;: car.GetColor(), }).Inc() If you using Prometheus, you will probably get an Error similar to this one:\n1:54PM WRN .../go/pkg/mod/github.com/go-masonry/[email protected]/monitoring/types.go:21 \u0026gt; monitoring error error=\u0026#34;inconsistent label cardinality: expected 1 label values but got 2 in prometheus.Labels{\\\u0026#34;color\\\u0026#34;:\\\u0026#34;blue\\\u0026#34;, \\\u0026#34;service\\\u0026#34;:\\\u0026#34;workshop\\\u0026#34;}\u0026#34; app=workshop git=9f00a9c host=Tals-Mac-mini.lan version=v1.2.3 The reason is that only one label is expected: service. It was added as a static label.\n You can extract values from a Context to override Declared Values.\ncounter.WithContext(ctx).Inc() For that you need to register Context Extractors for Metrics first, similar to Logging Context Extractors.\n Static Tags You can also add constant/static labels to every metric Implicitly.\nTo do that you need to add them in your configuration file.\nMortar will then automatically register all the tags and will add them to every metric you create.\nSetup To enable Monitoring you need to do a couple of things\n Configure your Monitoring implementation of choice.\n// PrometheusBuilder returns a monitor.Builder that is implemented by Prometheus func PrometheusBuilder(cfg cfg.Config) monitor.Builder { name := cfg.Get(confkeys.ApplicationName).String() return bprometheus.Builder().SetNamespace(name) } Create Uber-Fx options.\n// PrometheusFxOption registers prometheus func PrometheusFxOption() fx.Option { return fx.Options( providers.MonitorFxOption(), // Add a gRPC interceptor that times every gRPC incoming request, their respective metric name is: \u0026lt;application name\u0026gt;_grpc_\u0026lt;method\u0026gt;_bucket // Example: // # HELP workshop_grpc_Check time api calls for /mortar.health.v1.Health/Check // # TYPE workshop_grpc_Check histogram // workshop_grpc_Check_bucket{code=\u0026#34;0\u0026#34;,service=\u0026#34;workshop\u0026#34;,le=\u0026#34;0.005\u0026#34;} 2 // workshop_grpc_Check_bucket{code=\u0026#34;0\u0026#34;,service=\u0026#34;workshop\u0026#34;,le=\u0026#34;0.01\u0026#34;} 2 providers.MonitorGRPCInterceptorFxOption(), bprometheus.PrometheusInternalHandlerFxOption(), // This one exposes http://localhost:5382/metrics fx.Provide(PrometheusBuilder), ) } Provide everything.\nfx.New( mortar.PrometheusFxOption(), // Prometheus ) Prometheus Implementation If you chose to use Prometheus you can call http://localhost:5382/metrics to get a list of all the known metrics.\nGET /metrics HTTP/1.1 Accept: */* Accept-Encoding: gzip, deflate Connection: keep-alive Host: localhost:5382 # HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles. # TYPE go_gc_duration_seconds summary go_gc_duration_seconds{quantile=\u0026#34;0\u0026#34;} 3.6741e-05 go_gc_duration_seconds{quantile=\u0026#34;0.25\u0026#34;} 8.1574e-05 go_gc_duration_seconds{quantile=\u0026#34;0.5\u0026#34;} 0.000104209 go_gc_duration_seconds{quantile=\u0026#34;0.75\u0026#34;} 0.000121561 go_gc_duration_seconds{quantile=\u0026#34;1\u0026#34;} 0.000194707 go_gc_duration_seconds_sum 0.005753907 go_gc_duration_seconds_count 56 ... "
},
{
"uri": "https://go-masonry.github.io/middleware/telemetry/",
"title": "Telemetry",
"tags": [],
"description": "",
"content": "This section is about collecting telemetry data from your services and software. This data is usually forwarded to different analytic tools such as:\n Jaeger Prometheus ELK Mortar has different middleware to help you with that. It defines some interfaces and different middleware helpers, but not the implementations. Let\u0026rsquo;s break them into 3 different topics:\n Logging Monitoring Tracing Each topic is covered differently, but essentially they are somewhat connected.\n"
},
{
"uri": "https://go-masonry.github.io/fx/tests/",
"title": "Tests",
"tags": [],
"description": "",
"content": "Testing with Uber-FX makes it possible to test different logic while mocking parts of the DI graph.\nConstructor per Type While it\u0026rsquo;s possible to register several instances using one constructor function, If possible avoid this.\nOfficial Uber-FX documentation// Functions may also return multiple objects. For example, we could combine // NewHandler and NewLogger into a single function: // // func NewHandlerAndLogger() (*log.Logger, http.Handler, error) // // Fx also understands this idiom, and would treat NewHandlerAndLogger as the // constructor for both the *log.Logger and http.Handler types. Just like // constructors for a single type, NewHandlerAndLogger would be called at most // once, and both the handler and the logger would be cached and reused as // necessary. \n Make sure the constructor function returns only ONE Parameter and an optional Error. Let\u0026rsquo;s explain the reason behind that. During tests, sometimes you will want to swap a real dependency with a fake/mocked one. Currently, the real practical way to do it is NOT calling the real constructor function that creates this dependency, but call a different one that returns the same type. If your constructor function will return several types, you can\u0026rsquo;t really swap only one of them. You will need to swap all of them since you can\u0026rsquo;t call the real constructor.\n"
},
{
"uri": "https://go-masonry.github.io/middleware/telemetry/tracing/",
"title": "Tracing",
"tags": [],
"description": "",
"content": "Distributed tracing, also called distributed request tracing, is a method used to profile and monitor applications, especially those built using a microservices architecture. Distributed tracing helps pinpoint where failures occur and what causes poor performance.\nmermaid.initialize({startOnLoad:true}); graph LR A(A) B(B) C(C) D(D) E(E) F(F) A --|0.3ms| B A -- C B -- E C --|1s| D C -- F E --|0.5s| D Mortar has you covered, but we haven\u0026rsquo;t reinvented a wheel. Instead of defining a new Interface, you use a standard opentracing.Tracer Interface. It\u0026rsquo;s defined here.\ntype Tracer interface { StartSpan(operationName string, opts ...StartSpanOption) Span Inject(sm SpanContext, format interface{}, carrier interface{}) error Extract(format interface{}, carrier interface{}) (SpanContext, error) } Usage It\u0026rsquo;s best to StartSpan in a most outer layer of your Application. Since you will probably build a gRPC web service, gRPC ServerInterceptor is that spot.\nMortar have everything predefined already.\nPredefined Interceptors Most likely you will not have to add anything, but use these predefined Interceptors.\nServer If you examine grpc.UnaryServerInterceptor you can see that this Interceptor calls opentracing.StartSpanFromContextWithTracer which starts and returns a span with operationName using a span found within the context as a ChildOfRef. If that doesn\u0026rsquo;t exist it creates a root span. It also returns a context.Context object built around the returned span.\nThat\u0026rsquo;s great for gRPC communication, since everything will be found inside a Context. But what if your REST API is called ? Given that REST API is a reverse-proxy to your gRPC API, unless properly treated, the above code will create a new Server Span even if there is Tracing Information (usually found within HTTP Headers). To fix that we need to inject any tracing information into our Context.\nUnless you really have to, it\u0026rsquo;s best to handle everything on the gRPC layer.\n If your REST API is implemented by gRPC-Gateway, you should use this Metadata Trace Carrier.\nfunc HttpServerFxOptions() fx.Option { return fx.Options( providers.GRPCTracingUnaryServerInterceptorFxOption(), providers.GRPCGatewayMetadataTraceCarrierFxOption(), // read it\u0026#39;s documentation to understand better ) } Knowing what HTTP headers have this value depends on the Implementing Tracing library, Jaeger GO client knows how to do that :)\n To better understand how MetadataTraceCarrierOption works read this\n You can look at a working example here.\nClients You should get yourself familiar with Mortar Clients first.\nWhen calling remote services you need to pass your current Tracing information forward. Basically you have 2 options\n Create a Client Span for every remote call and then pass it. Just pass the Tracing Information forward. Mortar supports the first (with Client Span) option out-of-the-box.\n gRPC Client Interceptor HTTP Client Interceptor In order to use them, they must be provided to Uber-Fx first.\nfunc HttpClientFxOptions() fx.Option { return fx.Options( providers.HTTPClientBuildersFxOption(), // client builders providers.TracerGRPCClientInterceptorFxOption(), providers.TracerRESTClientInterceptorFxOption(), ) } Once provided you can use Mortar Clients as any other gRPC Clients.\n gRPC Client example can be found here HTTP Client example can be found here Adding dynamic info to current Span Sometime you need to add a Custom Tag or a Custom Log to the current Span.\nIt\u0026rsquo;s really easy and it\u0026rsquo;s not related to Mortar.\n To add a custom Tag:\nif span := opentracing.SpanFromContext(ctx); span != nil { span.SetTag(\u0026#34;custom\u0026#34;, \u0026#34;tag\u0026#34;) } To add a custom Log:\nif span := opentracing.SpanFromContext(ctx); span != nil { span.LogFields(spanLog.String(\u0026#34;custom\u0026#34;, \u0026#34;key\u0026#34;)) } Here is what it should look like in Jaeger.\n"
}]