- Makefile
- Examples
- Config
- Logger
- Service runner (goroutine manager) component
- Web services
- Tracing component
Production and kubernetes ready preconfigured library that contains several components for our projects
- configuration (based on env)
- logger (wrapped zap.SugaredLogger)
- service (runner manager for transport servers and workers)
- web (http / gRPC transport servers)
- tracer (jaeger)
Command | Description |
---|---|
help | Show this help |
deps | Ensure dependencies |
lint | Run Golang linters aggregator |
install-tools | Install tools needed for development |
test | Run all tests (pass package as argument if you want test specific one) |
Notice You don't need to use flags, all supported flags already exist in config
component.
We propose to use environment variables instead of custom project flags, configuration files or something else.
Usage:
-V, --version show current version
-h, --help show this help message
--markdown generate env markdown table
--validate validate config
Notice If you need add description for your custom application config option, just add it to field struct description, for example:
package main
import "github.com/im-kulikov/go-bones/config"
type CustomAppSettings struct {
config.Base // we propose to include base configuration
// App specific settings
MyParameter string `env:"MY_PARAMETER" usage:"custom description"`
MyStructure struct {
// -> MY_STRUCTURE_FIELD_ONE
FieldOne int `env:"FIELD_ONE" default:"10"`
// -> MY_STRUCTURE_FIELD_TWO
FieldTwo string `env:"FIELD_TWO" default:"some default value"`
}
}
Notice: You must include base configuration into app custom settings struct:
package main
import "github.com/im-kulikov/go-bones/config"
type CustomAppSettings struct {
config.Base
// AppSpecificSettings...
}
so you can use them out of the box, simple example of main.go
package main
import (
"context"
"os/signal"
"syscall"
"github.com/im-kulikov/go-bones/config"
"github.com/im-kulikov/go-bones/logger"
"github.com/im-kulikov/go-bones/service"
"github.com/im-kulikov/go-bones/tracer"
"github.com/im-kulikov/go-bones/web"
)
type settings struct {
config.Base
API web.HTTPConfig `env:"API"`
ShouldHaveDefaultValue int `env:"MY_KEY" default:"100500"`
}
var (
version = "dev"
appName = "example"
)
func (c settings) Validate(ctx context.Context) error {
// check that your fields is ok...
return c.Base.Validate(ctx)
}
func main() {
var cfg settings
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
defer cancel()
var err error
if err = config.Load(ctx, cfg.Base); err != nil {
logger.Default().Fatalf("could not prepare config: %s", err)
}
var log logger.Logger
if log, err = logger.New(cfg.Base.Logger,
logger.WithAppName(appName),
logger.WithAppVersion(version)); err != nil {
logger.Default().Fatalf("could not prepare logger: %s", err)
}
var trace service.Service
if trace, err = tracer.Init(log, cfg.Base.Tracer); err != nil {
log.Fatalf("could not initialize tracer: %s", err)
}
ops := web.NewOpsServer(log, cfg.Base.Ops)
group := service.New(log,
service.WithService(ops),
service.WithService(trace),
service.WithShutdownTimeout(cfg.Base.Shutdown))
if err = group.Run(context.Background()); err != nil {
log.Fatalf("something went wrong: %s", err)
}
}
Contains default components configurations and base flags.
-V, --version show current version
-h, --help show this help message
--markdown generate env markdown table
--validate validate config
Name | Required | Default value | Usage | Example |
---|---|---|---|---|
SHUTDOWN_TIMEOUT | false | 5s | allows to set custom graceful shutdown timeout | |
OPS_ENABLED | false | false | allows to enable ops server | |
OPS_ADDRESS | false | :8081 | allows to set set ops address:port | |
OPS_NETWORK | false | tcp | allows to set ops listen network: tcp/udp | |
OPS_NO_TRACE | false | true | allows to disable tracing | |
OPS_METRICS_PATH | false | /metrics | allows to set custom metrics path | |
OPS_HEALTHY_PATH | false | /healthy | allows to set custom healthy path | |
OPS_PROFILE_PATH | false | /debug/pprof | allows to set custom profiler path | |
LOGGER_ENCODING_CONSOLE | false | false | allows to set user-friendly formatting | |
LOGGER_LEVEL | false | info | allows to set custom logger level | |
LOGGER_TRACE | false | fatal | allows to set custom trace level | |
LOGGER_SAMPLE_RATE | false | 1000 | allows to set sample rate | |
TRACER_TYPE | false | jaeger | allows to set trace exporter type | |
TRACER_ENABLED | false | false | allows to enable tracing | |
TRACER_SAMPLER | false | 1 | allows to choose sampler | |
TRACER_ENDPOINT | false | allows to set jaeger endpoint (one of) | http://localhost:14268/api/traces | |
TRACER_AGENT_HOST | false | allows to set jaeger agent host (one of) | localhost | |
TRACER_AGENT_PORT | false | allows to set jaeger agent port | 6831 | |
TRACER_AGENT_RETRY_INTERVAL | false | 15s | allows to set retry connection timeout |
(one off) - you can provide TRACER_ENDPOINT or TRACER_AGENT_HOST
1. TRACER_ENDPOINT - used for HTTP jaeger exporter
2. TRACER_AGENT_HOST and TRACER_AGENT_PORT - used for UDP exporter
Contains preconfigured logger.Logger
package main
import (
"github.com/im-kulikov/go-bones/logger"
)
var version string
func main() {
sample := 1000
loggerConfig := logger.Config{
EncodingConsole: true,
Level: "info",
Trace: "fatal",
SampleRate: &sample,
}
log, err := logger.New(loggerConfig,
logger.WithAppName("name"),
logger.WithAppVersion(version))
if err != nil {
logger.Default().Fatalf("could not prepare logger: %s", err)
}
_ = log
// ...
}
It allows concentrate on business logic and just pass services (Start/Stop/Name interface) into it.
package main
import (
"context"
"errors"
"os/signal"
"time"
"github.com/im-kulikov/go-bones/logger"
"github.com/im-kulikov/go-bones/service"
)
type web struct{ service.Service }
type ops struct{ service.Service }
type run struct{ service.Service }
const shutdownTimeout = time.Second * 5
var errToIgnore = errors.New("should be ignored")
var (
_ service.Service = (*web)(nil)
_ service.Service = (*ops)(nil)
_ service.Service = (*run)(nil)
)
func main() {
runService := new(run)
webService := new(web)
opsService := new(ops)
group := service.New(logger.Default(),
service.WithService(runService),
service.WithService(webService),
service.WithService(opsService),
service.WithIgnoreError(errToIgnore),
service.WithShutdownTimeout(shutdownTimeout))
ctx, cancel := signal.NotifyContext(context.Background())
defer cancel()
if err := group.Run(ctx); err != nil {
panic(err)
}
}
Allows concentrate on business logic and use preconfigured http / gRPC services.
Contains next handlers (can be changed by configuration)
- /healthy
- /metrics
- /debug/pprof
package main
import (
"github.com/im-kulikov/go-bones/logger"
"github.com/im-kulikov/go-bones/web"
"go.uber.org/zap"
)
func main() {
log := logger.Default()
ops := web.NewOpsServer(log, web.OpsConfig{
Enabled: true,
Address: ":8081",
Network: "tcp",
NoTrace: false,
HealthyPath: "/custom-healthy-path",
MetricsPath: "/custom-metrics-path",
ProfilePath: "/custom-profile-path",
}, ...web.HealthChecker)
// http.Server with healthy, metrics and profiler and
// HealthChecker's worker that run health check for each passed component
_ = ops
// ...
}
package main
import (
"net/http"
"github.com/im-kulikov/go-bones/web"
)
func router() http.Handler { panic("implement me") }
func main() {
handler := router()
custom := web.NewHTTPServer(
web.WithHTTPName("custom"),
web.WithHTTPHandler(handler),
web.WithHTTPConfig(web.HTTPConfig{
Enabled: true,
Address: ":8080",
Network: "tcp",
}))
_ = custom
// ...
}
package main
import (
"github.com/im-kulikov/go-bones/web"
)
type service struct {
// implement me
web.GRPCService
}
// implement me.
func newService() web.GRPCService {
return new(service)
}
func main() {
service1 := newService()
service2 := newService()
service3 := newService()
custom := web.NewGRPCServer(
web.WithGRPCName("custom"),
web.WithGRPCService(service1),
web.WithGRPCService(service2),
web.WithGRPCService(service3),
web.WithGRPCConfig(web.GRPCConfig{
Enabled: true,
Reflect: true, // enables reflection service
Address: ":9090",
Network: "tcp",
}))
_ = custom
// ...
}
You can find more information about tracing conventions in public documentation
package main
import (
"time"
"github.com/im-kulikov/go-bones/logger"
"github.com/im-kulikov/go-bones/service"
"github.com/im-kulikov/go-bones/tracer"
)
func main() {
var err error
cfg := tracer.Config{
Type: tracer.JaegerType,
Enabled: true,
Jaeger: tracer.Jaeger{
Sampler: 1,
Service: "custom-service",
Endpoint: "http://jaeger-endpoint",
AgentEndpoint: "jaeger-udp-endpoint:6831",
RetryInterval: time.Second * 15,
},
}
var trace service.Service
if trace, err = tracer.Init(logger.Default(), cfg); err != nil { // ... tracer.Option)
logger.Default().Fatalf("could not initialize tracing %v", err)
}
_ = trace
}
Without context
package main
import (
gprom "github.com/grpc-ecosystem/go-grpc-prometheus"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"github.com/im-kulikov/go-bones/tracer"
)
func main() {
conn, err := grpc.Dial("localhost:8080",
// prometheus and tracing enabling:
grpc.WithChainUnaryInterceptor(gprom.UnaryClientInterceptor, otelgrpc.UnaryClientInterceptor()),
grpc.WithChainStreamInterceptor(gprom.StreamClientInterceptor, otelgrpc.StreamClientInterceptor()),
// example of custom client dial options
grpc.WithBlock(),
grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
}
defer conn.Close()
// do something with gRPC connection...
}
With context
package main
import (
"context"
gprom "github.com/grpc-ecosystem/go-grpc-prometheus"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"github.com/im-kulikov/go-bones/tracer"
)
func main() {
ctx, cancel := context.WithCancel(context.TODO())
defer cancel()
conn, err := grpc.DialContext(ctx, "localhost:8080",
// prometheus and tracing enabling:
grpc.WithChainUnaryInterceptor(gprom.UnaryClientInterceptor, otelgrpc.UnaryClientInterceptor()),
grpc.WithChainStreamInterceptor(gprom.StreamClientInterceptor, otelgrpc.StreamClientInterceptor()),
// example of custom client dial options
grpc.WithBlock(),
grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
}
defer conn.Close()
// do something with gRPC connection...
}
package main
import (
"context"
"net"
"time"
gprom "github.com/grpc-ecosystem/go-grpc-prometheus"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/keepalive"
"github.com/im-kulikov/go-bones/tracer"
)
func main() {
srv := grpc.NewServer(
// prometheus and tracing enabling:
grpc.ChainUnaryInterceptor(gprom.UnaryServerInterceptor, otelgrpc.UnaryServerInterceptor()),
grpc.ChainStreamInterceptor(gprom.StreamServerInterceptor, otelgrpc.StreamServerInterceptor()),
// for example of custom server options
grpc.KeepaliveParams(keepalive.ServerParameters{
Timeout: time.Second * 30,
}))
ctx, cancel := context.WithCancel(context.TODO())
defer cancel()
lis, err := new(net.ListenConfig).Listen(ctx, "tcp", ":8080")
if err != nil {
panic(err)
}
if err = srv.Serve(lis); err != nil {
panic(err)
}
}
package main
import (
"context"
"net/http"
"github.com/im-kulikov/go-bones/tracer"
"github.com/im-kulikov/go-bones/web"
)
func main() {
cli := http.DefaultClient
web.ApplyTracingToHTTPClient(cli)
ctx, cancel := context.WithCancel(context.TODO())
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://google.com", nil)
if err != nil {
panic(err)
}
if _, err = cli.Do(req); err != nil {
panic(err)
}
}
package main
import (
"net/http"
"net/http/pprof"
"github.com/im-kulikov/go-bones/web"
)
func main() {
mux := http.NewServeMux()
// with http.Handler
mux.Handle("/heap", web.HTTPTracingMiddleware(pprof.Handler("heap")))
// with http.HandlerFunc
mux.Handle("/test", web.HTTPTracingMiddlewareFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
if err := http.ListenAndServe(":8080", mux); err != nil {
panic(err)
}
}