Categorygithub.com/im-kulikov/go-bones
modulepackage
0.0.0-20230922220622-8993a07c76e7
Repository: https://github.com/im-kulikov/go-bones.git
Documentation: pkg.go.dev

# README

Golang bones

Codecov Build Status Report GitHub release GitHub Dependabot Status

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)

Makefile

CommandDescription
helpShow this help
depsEnsure dependencies
lintRun Golang linters aggregator
install-toolsInstall tools needed for development
testRun all tests (pass package as argument if you want test specific one)

Examples

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)
    }
}

Config

Contains default components configurations and base flags.

Base flags

  -V, --version    show current version
  -h, --help       show this help message
      --markdown   generate env markdown table
      --validate   validate config

Envs

NameRequiredDefault valueUsageExample
SHUTDOWN_TIMEOUTfalse5sallows to set custom graceful shutdown timeout
OPS_ENABLEDfalsefalseallows to enable ops server
OPS_ADDRESSfalse:8081allows to set set ops address:port
OPS_NETWORKfalsetcpallows to set ops listen network: tcp/udp
OPS_NO_TRACEfalsetrueallows to disable tracing
OPS_METRICS_PATHfalse/metricsallows to set custom metrics path
OPS_HEALTHY_PATHfalse/healthyallows to set custom healthy path
OPS_PROFILE_PATHfalse/debug/pprofallows to set custom profiler path
LOGGER_ENCODING_CONSOLEfalsefalseallows to set user-friendly formatting
LOGGER_LEVELfalseinfoallows to set custom logger level
LOGGER_TRACEfalsefatalallows to set custom trace level
LOGGER_SAMPLE_RATEfalse1000allows to set sample rate
TRACER_TYPEfalsejaegerallows to set trace exporter type
TRACER_ENABLEDfalsefalseallows to enable tracing
TRACER_SAMPLERfalse1allows to choose sampler
TRACER_ENDPOINTfalseallows to set jaeger endpoint (one of)http://localhost:14268/api/traces
TRACER_AGENT_HOSTfalseallows to set jaeger agent host (one of)localhost
TRACER_AGENT_PORTfalseallows to set jaeger agent port6831
TRACER_AGENT_RETRY_INTERVALfalse15sallows 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

Logger

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

    // ...
}

Service runner (goroutine manager) component

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)
    }
}

Web services

Allows concentrate on business logic and use preconfigured http / gRPC services.

OPS service

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

    // ...
}

HTTP custom service

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

    // ...
}

gRPC custom service

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

    // ...
}

Tracing component

You can find more information about tracing conventions in public documentation

Preconfigured Jaeger

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
}

gRPC

Examples for grpc.Conn

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...
}

Examples for grpc.Server

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)
    }
}

HTTP

Examples for http.Client

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)
    }
}

Examples for http.Server

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)
    }
}

# Packages

No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author

# Functions

ErrorCode returns the code of the error, if available.

# Constants

ErrorCodeInternal is an internal error code.

# Structs

Error represents an error within the context of go-bones service.