{"id":23209652,"url":"https://github.com/acronis/go-appkit","last_synced_at":"2025-08-19T04:32:30.044Z","repository":{"id":252362094,"uuid":"838930308","full_name":"acronis/go-appkit","owner":"acronis","description":"Common Go packages for writing applications, services, and tools","archived":false,"fork":false,"pushed_at":"2024-12-13T15:39:30.000Z","size":5274,"stargazers_count":19,"open_issues_count":0,"forks_count":10,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-12-13T17:15:54.142Z","etag":null,"topics":["appkit","distributed-systems","go","golang","http","micro-framework","microservices","rest"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/acronis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-08-06T16:08:40.000Z","updated_at":"2024-12-13T15:39:35.000Z","dependencies_parsed_at":"2024-10-22T15:06:17.554Z","dependency_job_id":null,"html_url":"https://github.com/acronis/go-appkit","commit_stats":null,"previous_names":["acronis/go-libs"],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acronis%2Fgo-appkit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acronis%2Fgo-appkit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acronis%2Fgo-appkit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acronis%2Fgo-appkit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/acronis","download_url":"https://codeload.github.com/acronis/go-appkit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230318687,"owners_count":18207831,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["appkit","distributed-systems","go","golang","http","micro-framework","microservices","rest"],"created_at":"2024-12-18T18:18:56.455Z","updated_at":"2025-08-19T04:32:29.986Z","avatar_url":"https://github.com/acronis.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Common Go packages for writing applications, services, and tools\n\n[![GoDoc Widget]][GoDoc]\n\nThe project includes the following packages:\n\n+ [config](./config) - loading configuration from environment variables, files, and `io.Reader`. YAML and JSON formats are supported out of the box.\n+ [grpcserver](./grpcserver) - gRPC server with configuration from YAML/JSON files, built-in interceptors, and observability features.\n+ [grpcserver/interceptor](./grpcserver/interceptor) - collection of gRPC interceptors for logging, metrics collection, panic recovery, request-id tracing, rate and in-flight request limiting, etc.\n+ [grpcserver/interceptor/throttle](./grpcserver/interceptor) - ready-to-use interceptors for gRPC throttling that can be flexibly configured via JSON or YAML.\n+ [httpclient](./httpclient) - helpers and a set of `http.RoundTripper` implementations for simplifying typical HTTP client operations (e.g. retries, client-side throttling, setting any header for each request, etc.).\n+ [httpserver](./httpserver) - configurable HTTP server (wrapper around `http.Server`) that includes graceful shutdown support, panic recovery, metrics collection, and logging.\n+ [httpserver/middleware](./httpserver/middleware) - collection of middlewares for HTTP server (e.g. request logging, metrics collection, panic recovery, rate and in-flight request limiting, request-id tracing, etc.).\n+ [httpserver/middleware/throttle](./httpserver/middleware/throttle) - ready-to-use middleware for server-side throttling that can be flexibly configured via JSON or YAML.\n+ [log](./log) - unified interface for structured logging with included configurable adapter for tiny, fast, and memory-efficient (zero-allocation) [logf](https://github.com/ssgreg/logf) logger.\n+ [lrucache](./lrucache) - in-memory LRU cache with collecting Prometheus metrics.\n+ [netutil](./netutil) - utilities for working with network.\n+ [profserver](./profserver) - profiling HTTP server (pprof).\n+ [restapi](./restapi) - set of simple functions for doing requests and sending responses in the REST API.\n+ [retry](./restapi) - helper functions for doing retryable operations.\n+ [service](./service) - ready-to-use primitives for creating services and managing their lifecycle.\n+ [testutil](./testutil) - helpers for writing tests.\n\n## Installation\n\n```\ngo get -u github.com/acronis/go-appkit\n```\n\n## Examples\n\n### Simple service that provides HTTP API\n\nThe following example demonstrates how to create a simple service with\n+ Configuration loading from environment variables and a yaml file.\n+ HTTP server with request logging, metrics collection, primitive tracing and versioned API endpoints.\n+ Profiling server (pprof) for debugging purposes running on a separate port.\n+ Graceful shutdown on SIGTERM and SIGINT signals.\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\tgolog \"log\"\n\t\"net/http\"\n\n\t\"github.com/go-chi/chi/v5\"\n\n\t\"github.com/acronis/go-appkit/config\"\n\t\"github.com/acronis/go-appkit/httpserver\"\n\t\"github.com/acronis/go-appkit/httpserver/middleware\"\n\t\"github.com/acronis/go-appkit/log\"\n\t\"github.com/acronis/go-appkit/profserver\"\n\t\"github.com/acronis/go-appkit/restapi\"\n\t\"github.com/acronis/go-appkit/service\"\n)\n\nfunc main() {\n\tif err := runApp(); err != nil {\n\t\tgolog.Fatal(err)\n\t}\n}\n\nfunc runApp() error {\n\tcfg, err := loadConfigFromFile(\"config.yaml\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"load config: %w\", err)\n\t}\n\n\tlogger, closeFn := log.NewLogger(cfg.Log)\n\tdefer closeFn()\n\n\tvar serviceUnits []service.Unit\n\n\t// Create HTTP server that provides /healthz, /metrics, and /api/{service-name}/v{number}/* endpoints.\n\thttpServer, err := makeHTTPServer(cfg.Server, logger)\n\tif err != nil {\n\t\treturn err\n\t}\n\tserviceUnits = append(serviceUnits, httpServer)\n\n\tif cfg.ProfServer.Enabled {\n\t\t// Create HTTP server for profiling (pprof is used under the hood).\n\t\tserviceUnits = append(serviceUnits, profserver.New(cfg.ProfServer, logger))\n\t}\n\n\treturn service.New(logger, service.NewCompositeUnit(serviceUnits...)).Start()\n}\n\nfunc makeHTTPServer(cfg *httpserver.Config, logger log.FieldLogger) (*httpserver.HTTPServer, error) {\n\tconst errorDomain = \"MyService\" // Error domain is useful for distinguishing errors from different services (e.g. proxies).\n\n\tapiRoutes := map[httpserver.APIVersion]httpserver.APIRoute{\n\t\t1: func(router chi.Router) {\n\t\t\trouter.Get(\"/hello\", v1HelloHandler())\n\t\t},\n\t\t2: func(router chi.Router) {\n\t\t\trouter.Get(\"/hi\", v2HiHandler(errorDomain))\n\t\t},\n\t}\n\n\topts := httpserver.Opts{\n\t\tServiceNameInURL: \"my-service\",\n\t\tErrorDomain:      errorDomain,\n\t\tAPIRoutes:        apiRoutes,\n\t\tHealthCheck: func() (httpserver.HealthCheckResult, error) {\n\t\t\t// 503 status code will be returned if any of the components is unhealthy.\n\t\t\treturn map[httpserver.HealthCheckComponentName]httpserver.HealthCheckStatus{\n\t\t\t\t\"component-a\": httpserver.HealthCheckStatusOK,\n\t\t\t\t\"component-b\": httpserver.HealthCheckStatusOK,\n\t\t\t}, nil\n\t\t},\n\t}\n\n\thttpServer, err := httpserver.New(cfg, logger, opts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Custom routes can be added using chi.Router directly.\n\thttpServer.HTTPRouter.Get(\"/custom-route\", customRouteHandler)\n\n\treturn httpServer, nil\n}\n\nfunc loadConfigFromFile(filePath string) (*AppConfig, error) {\n\tcfg := NewAppConfig()\n\terr := config.NewDefaultLoader(\"my_service\").LoadFromFile(filePath, config.DataTypeYAML, cfg)\n\treturn cfg, err\n}\n\nfunc v1HelloHandler() func(rw http.ResponseWriter, r *http.Request) {\n\treturn func(rw http.ResponseWriter, r *http.Request) {\n\t\tlogger := middleware.GetLoggerFromContext(r.Context())\n\t\trestapi.RespondJSON(rw, map[string]string{\"message\": \"Hello from v1\"}, logger)\n\t}\n}\n\nfunc v2HiHandler(errorDomain string) func(rw http.ResponseWriter, r *http.Request) {\n\treturn func(rw http.ResponseWriter, r *http.Request) {\n\t\tlogger := middleware.GetLoggerFromContext(r.Context())\n\t\tname := r.URL.Query().Get(\"name\")\n\t\tif len(name) \u003c 3 {\n\t\t\tapiErr := restapi.NewError(errorDomain, \"invalidName\", \"Name must be at least 3 characters long\")\n\t\t\trestapi.RespondError(rw, http.StatusBadRequest, apiErr, middleware.GetLoggerFromContext(r.Context()))\n\t\t\treturn\n\t\t}\n\t\trestapi.RespondJSON(rw, map[string]string{\"message\": fmt.Sprintf(\"Hi %s from v2\", name)}, logger)\n\t}\n}\n\nfunc customRouteHandler(rw http.ResponseWriter, r *http.Request) {\n\tlogger := middleware.GetLoggerFromContext(r.Context())\n\tif _, err := rw.Write([]byte(\"Content from the custom route\")); err != nil {\n\t\tlogger.Error(\"error while writing response body\", log.Error(err))\n\t}\n}\n\ntype AppConfig struct {\n\tServer     *httpserver.Config\n\tProfServer *profserver.Config\n\tLog        *log.Config\n}\n\nfunc NewAppConfig() *AppConfig {\n\treturn \u0026AppConfig{\n\t\tServer:     httpserver.NewConfig(),\n\t\tProfServer: profserver.NewConfig(),\n\t\tLog:        log.NewConfig(),\n\t}\n}\n\nfunc (c *AppConfig) SetProviderDefaults(dp config.DataProvider) {\n\tconfig.CallSetProviderDefaultsForFields(c, dp)\n}\n\nfunc (c *AppConfig) Set(dp config.DataProvider) error {\n\treturn config.CallSetForFields(c, dp)\n}\n```\n\nConfiguration file `config.yaml`:\n\n```yaml\nserver:\n  address: \":8888\"\n  timeouts:\n    write: 1m\n    read: 15s\n    readHeader: 10s\n    idle: 1m\n    shutdown: 5s\n  limits:\n    maxBodySize: 1M\n  log:\n    requestStart: true\nprofServer:\n  enabled: true\n  address: \":8889\"\nlog:\n  level: info\n  format: json\n  output: stdout\n```\n\nRun the service:\n\n```shell\n$ go run main.go\n```\n\nCheck the service API:\n\n```shell\n$ curl -w \"\\nHTTP code: %{http_code}\\n\" localhost:8888/api/my-service/v1/hello                                                                                                                                                               [130]\n{\"message\":\"Hello\"}\nHTTP code: 200\n\n$ curl -w \"\\nHTTP code: %{http_code}\\n\" 'localhost:8888/api/my-service/v2/hi?name='\n{\"error\":{\"domain\":\"MyService\",\"code\":\"invalidName\",\"message\":\"Name must be at least 3 characters long\"}}\nHTTP code: 400\n\n$ curl -w \"\\nHTTP code: %{http_code}\\n\" 'localhost:8888/api/my-service/v2/hi?name=Alice'\n{\"message\":\"Hi Alice\"}\nHTTP code: 200\n```\n\nCheck the service health and metrics:\n```shell\n\n$ curl -w \"\\nHTTP code: %{http_code}\\n\" localhost:8888/healthz\n{\"components\":{\"component-a\":true,\"component-b\":true}}\nHTTP code: 200\n\n$ curl localhost:8888/metrics\n...\n# HELP http_request_duration_seconds A histogram of the HTTP request durations.\n# TYPE http_request_duration_seconds histogram\nhttp_request_duration_seconds_bucket{method=\"GET\",route_pattern=\"/api/my-service/v1/hello\",status_code=\"200\",user_agent_type=\"http-client\",le=\"0.01\"} 1\n...\nhttp_request_duration_seconds_bucket{method=\"GET\",route_pattern=\"/api/my-service/v1/hello\",status_code=\"200\",user_agent_type=\"http-client\",le=\"600\"} 1\nhttp_request_duration_seconds_bucket{method=\"GET\",route_pattern=\"/api/my-service/v1/hello\",status_code=\"200\",user_agent_type=\"http-client\",le=\"+Inf\"} 1\nhttp_request_duration_seconds_sum{method=\"GET\",route_pattern=\"/api/my-service/v1/hello\",status_code=\"200\",user_agent_type=\"http-client\"} 0.000184125\nhttp_request_duration_seconds_count{method=\"GET\",route_pattern=\"/api/my-service/v1/hello\",status_code=\"200\",user_agent_type=\"http-client\"} 1\n...\nhttp_request_duration_seconds_bucket{method=\"GET\",route_pattern=\"/api/my-service/v2/hi\",status_code=\"400\",user_agent_type=\"http-client\",le=\"0.01\"} 1\n...\nhttp_request_duration_seconds_bucket{method=\"GET\",route_pattern=\"/api/my-service/v2/hi\",status_code=\"400\",user_agent_type=\"http-client\",le=\"600\"} 1\nhttp_request_duration_seconds_bucket{method=\"GET\",route_pattern=\"/api/my-service/v2/hi\",status_code=\"400\",user_agent_type=\"http-client\",le=\"+Inf\"} 1\nhttp_request_duration_seconds_sum{method=\"GET\",route_pattern=\"/api/my-service/v2/hi\",status_code=\"400\",user_agent_type=\"http-client\"} 0.000326041\nhttp_request_duration_seconds_count{method=\"GET\",route_pattern=\"/api/my-service/v2/hi\",status_code=\"400\",user_agent_type=\"http-client\"} 1\n...\n# HELP http_requests_in_flight Current number of HTTP requests being served.\n# TYPE http_requests_in_flight gauge\nhttp_requests_in_flight{method=\"GET\",route_pattern=\"/api/my-service/v1/hello\",user_agent_type=\"http-client\"} 0\nhttp_requests_in_flight{method=\"GET\",route_pattern=\"/api/my-service/v2/hi\",user_agent_type=\"http-client\"} 0\n...\n```\n\nService logs:\n```\n{\"level\":\"info\",\"time\":\"2024-06-04T20:22:01.862351+03:00\",\"msg\":\"starting application HTTP server...\",\"pid\":8455,\"address\":\":8888\",\"write_timeout\":\"1m0s\",\"read_timeout\":\"15s\",\"read_header_timeout\":\"10s\",\"idle_timeout\":\"1m0s\",\"shutdown_timeout\":\"\n5s\"}\n{\"level\":\"info\",\"time\":\"2024-06-04T20:22:01.862516+03:00\",\"msg\":\"starting profiling HTTP server...\",\"pid\":8455,\"address\":\":8889\"}\n...\n{\"level\":\"info\",\"time\":\"2024-06-04T20:22:08.075376+03:00\",\"msg\":\"request started\",\"pid\":8455,\"request_id\":\"cpfkqg3juspi21pmber0\",\"int_request_id\":\"cpfkqg3juspi21pmberg\",\"trace_id\":\"\",\"method\":\"GET\",\"uri\":\"/api/my-service/v1/hello\",\"remote_addr\":\"[::1]:59994\",\"content_length\":0,\"user_agent\":\"curl/8.6.0\",\"remote_addr_ip\":\"::1\",\"remote_addr_port\":59994}\n{\"level\":\"info\",\"time\":\"2024-06-04T20:22:08.075518+03:00\",\"msg\":\"response completed in 0.000s\",\"pid\":8455,\"request_id\":\"cpfkqg3juspi21pmber0\",\"int_request_id\":\"cpfkqg3juspi21pmberg\",\"trace_id\":\"\",\"method\":\"GET\",\"uri\":\"/api/my-service/v1/hello\",\"remote_addr\":\"[::1]:59994\",\"content_length\":0,\"user_agent\":\"curl/8.6.0\",\"remote_addr_ip\":\"::1\",\"remote_addr_port\":59994,\"duration_ms\":0,\"duration\":184,\"status\":200,\"bytes_sent\":19}\n...\n{\"level\":\"info\",\"time\":\"2024-06-04T20:31:14.002993+03:00\",\"msg\":\"service got signal\",\"pid\":8455,\"signal\":\"interrupt\"}\n{\"level\":\"info\",\"time\":\"2024-06-04T20:31:14.003051+03:00\",\"msg\":\"closing profiling HTTP server...\",\"pid\":8455}\n{\"level\":\"info\",\"time\":\"2024-06-04T20:31:14.00321+03:00\",\"msg\":\"profiling HTTP served closed\",\"pid\":8455,\"address\":\":8889\"}\n{\"level\":\"info\",\"time\":\"2024-06-04T20:31:14.003254+03:00\",\"msg\":\"shutting down application HTTP server...\",\"pid\":8455,\"timeout\":\"5s\"}\n{\"level\":\"info\",\"time\":\"2024-06-04T20:31:14.003365+03:00\",\"msg\":\"application HTTP server closed\",\"pid\":8455,\"address\":\":8888\",\"write_timeout\":\"1m0s\",\"read_timeout\":\"15s\",\"read_header_timeout\":\"10s\",\"idle_timeout\":\"1m0s\",\"shutdown_timeout\":\"5s\"}\n{\"level\":\"info\",\"time\":\"2024-06-04T20:31:14.003412+03:00\",\"msg\":\"application HTTP server shut down\",\"pid\":8455}\n```\n\n## License\n\nCopyright © 2024 Acronis International GmbH.\n\nLicensed under [MIT License](./LICENSE).\n\n[GoDoc]: https://pkg.go.dev/github.com/acronis/go-appkit\n[GoDoc Widget]: https://godoc.org/github.com/acronis/go-appkit?status.svg\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Facronis%2Fgo-appkit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Facronis%2Fgo-appkit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Facronis%2Fgo-appkit/lists"}