Mortar is a GO framework/library for building gRPC (and REST) web services. Mortar has out-of-the-box support for configuration, application metrics, logging, tracing, profiling, dependency injection and more. While it comes with predefined defaults, Mortar gives you total control to fully customize it. |
---|
Clone this demo repository to better understand some of Mortar capabilities.
When you done, read the documentation.
To help you bootstrap your services with Mortar here you can find a template. Read its README first.
-
Bundled Grpc-Gateway (REST Reverse-Proxy).
-
Dependency Injection using Uber-FX.
-
Pimped
*http.Client
with interceptors support. -
Abstract support for Logging, Configuration, Tracing and Monitoring libraries. Use provided wrappers or your own.
- Jaeger wrapper client for tracing.
- Prometheus wrapper client for monitoring/metrics.
- Zerolog wrapper for logging.
- Viper wrapper for configuration.
-
Internal HTTP Handlers
- Profiling
http://.../debug/pprof
- Debug
http://.../debug/*
- Configuration
http://.../self/config
- Build Information
http://.../self/build
- Health
http://.../health
- Profiling
-
Server/Client Interceptors both for gRPC and HTTP, you can choose which to use and/or add your own.
Some examples
- HTTP Headers can be forwarded to next hop, defined by list.
- HTTP Headers can be included in logs, defined by list.
- Made available in
ctx.Context
via gRPC incoming Metadata. - Automatic monitoring and tracing (if enabled) for every RPC defined by the API.
...and more.
-
Logs have Tracing Information
traceId=6ff7e7e38d1e86f
across services -
Also visible in Jaeger
traceId=6ff7e7e38d1e86f
if it's sampled.
-
Add request and response info to Trace
-
Log/Dump requests and/or responses when http request fails.
return func(req *http.Request, handler client.HTTPHandler) (resp *http.Response, err error) { var reqBytes, respBytes []byte // If the response is Bad Request, log both Request and Response reqBytes, _ = httputil.DumpRequestOut(req, true) // it can be nil and it's ok if resp, err = handler(req); err == nil && resp.StatusCode >= http.StatusBadRequest { respBytes, _ = httputil.DumpResponse(resp, true) // it can be nil logger.WithError(fmt.Errorf("http request failed")). WithField("status",resp.StatusCode). Warn(req.Context(), "\nRequest:\n%s\n\nResponse:\n%s\n", reqBytes, respBytes) } return }
-
Alter requests and/or responses (useful in Tests)
func(*http.Request, clientInt.HTTPHandler) (*http.Response, error) { // special case, don't go anywhere just return the response return &http.Response{ Status: "200 OK", StatusCode: 200, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, ContentLength: 11, Body: ioutil.NopCloser(strings.NewReader("car painted")), }, nil }
Export to either Prometheus/Datadog/statsd/etc, it's your choice. Mortar only provides the Interface and also caches the metrics so you don't have to.
counter := w.deps.Metrics.WithTags(monitor.Tags{
"color": request.GetDesiredColor(),
"success": fmt.Sprintf("%t", err == nil),
}).Counter("paint_desired_color", "New paint color for car")
counter.Inc()
counter
is actually a singleton, uniqueness calculated here
For more information about Mortar Monitoring read here.
/debug/pprof
and other useful handlers- Use
config_test.yml
during tests to override values inconfig.yml
, it saves time.
There are some features not listed here, please check the Documentation for more.
Mortar is not a drop-in replacement.
It's important to read its Documentation first.