{"id":13411138,"url":"https://github.com/lalamove/konfig","last_synced_at":"2026-01-16T05:05:58.270Z","repository":{"id":55587937,"uuid":"166444234","full_name":"lalamove/konfig","owner":"lalamove","description":"Composable, observable and performant config handling for Go for the distributed processing era","archived":false,"fork":false,"pushed_at":"2020-10-28T08:24:08.000Z","size":289,"stargazers_count":651,"open_issues_count":5,"forks_count":55,"subscribers_count":16,"default_branch":"master","last_synced_at":"2024-07-31T20:44:51.785Z","etag":null,"topics":["composable","config","configuration","etcd","golang","golang-package","kubernetes","polyglot","reloading","state-management","vault"],"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/lalamove.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":null,"security":null,"support":null}},"created_at":"2019-01-18T17:03:03.000Z","updated_at":"2024-07-29T17:18:54.000Z","dependencies_parsed_at":"2022-08-15T03:50:16.999Z","dependency_job_id":null,"html_url":"https://github.com/lalamove/konfig","commit_stats":null,"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"purl":"pkg:github/lalamove/konfig","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lalamove%2Fkonfig","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lalamove%2Fkonfig/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lalamove%2Fkonfig/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lalamove%2Fkonfig/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lalamove","download_url":"https://codeload.github.com/lalamove/konfig/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lalamove%2Fkonfig/sbom","scorecard":{"id":577617,"data":{"date":"2025-08-11","repo":{"name":"github.com/lalamove/konfig","commit":"5b61527a91b747aff99d250d93d2ba3fc4acc0e4"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.8,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":5,"reason":"Found 7/13 approved changesets -- score normalized to 5","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 29 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"31 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GO-2020-0017 / GHSA-w73w-5m7g-f7qc","Warn: Project is vulnerable to: GO-2022-0322 / GHSA-cg3q-j54f-5p7p","Warn: Project is vulnerable to: GHSA-2xhq-gv6c-p224","Warn: Project is vulnerable to: GHSA-wr2v-9rpq-c35q","Warn: Project is vulnerable to: GHSA-wf43-55jj-vwq8","Warn: Project is vulnerable to: GO-2024-2528","Warn: Project is vulnerable to: GO-2024-2529","Warn: Project is vulnerable to: GO-2024-2530","Warn: Project is vulnerable to: GO-2022-0229 / GHSA-cjjc-xp8v-855w","Warn: Project is vulnerable to: GO-2020-0012 / GHSA-ffhg-7mh4-33c4","Warn: Project is vulnerable to: GO-2021-0227 / GHSA-3vm4-22fp-5rfm","Warn: Project is vulnerable to: GO-2022-0968 / GHSA-gwc9-m7rh-j2ww","Warn: Project is vulnerable to: GO-2021-0356 / GHSA-8c26-wmh5-6g9v","Warn: Project is vulnerable to: GO-2024-2961","Warn: Project is vulnerable to: GO-2023-2402 / GHSA-45x7-px36-x8w8","Warn: Project is vulnerable to: GO-2024-3321 / GHSA-v778-237x-gjrc","Warn: Project is vulnerable to: GO-2025-3487 / GHSA-hcg3-q754-cr77","Warn: Project is vulnerable to: GO-2022-0236 / GHSA-h86h-8ppg-mxmh","Warn: Project is vulnerable to: GO-2021-0238 / GHSA-83g2-8m93-v3w7","Warn: Project is vulnerable to: GO-2022-0288","Warn: Project is vulnerable to: GO-2022-0969 / GHSA-69cg-p879-7622","Warn: Project is vulnerable to: GO-2022-1144 / GHSA-xrjj-mj9h-534m","Warn: Project is vulnerable to: GO-2023-1571 / GHSA-vvpx-j8f3-3w6h","Warn: Project is vulnerable to: GO-2023-1988 / GHSA-2wrh-6pvc-2jm9","Warn: Project is vulnerable to: GO-2023-2102 / GHSA-4374-p667-p6c8","Warn: Project is vulnerable to: GO-2023-2153 / GHSA-m425-mq94-257g / GHSA-qppj-fm5r-hxr3","Warn: Project is vulnerable to: GO-2024-2687 / GHSA-4v7x-pqxf-cx7m","Warn: Project is vulnerable to: GO-2024-3333","Warn: Project is vulnerable to: GO-2025-3503 / GHSA-qxp5-gwg8-xv66","Warn: Project is vulnerable to: GO-2025-3595 / GHSA-vvgc-356p-c3xw","Warn: Project is vulnerable to: GO-2024-2631 / GHSA-c5q2-7r4c-mv6g"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-20T18:15:48.169Z","repository_id":55587937,"created_at":"2025-08-20T18:15:48.170Z","updated_at":"2025-08-20T18:15:48.170Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28477210,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T03:13:13.607Z","status":"ssl_error","status_checked_at":"2026-01-16T03:11:47.863Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["composable","config","configuration","etcd","golang","golang-package","kubernetes","polyglot","reloading","state-management","vault"],"created_at":"2024-07-30T20:01:11.658Z","updated_at":"2026-01-16T05:05:58.255Z","avatar_url":"https://github.com/lalamove.png","language":"Go","funding_links":[],"categories":["Go","Configuration","配置","配置管理","配置管理 `配置解析库`","Uncategorized"],"sub_categories":["Standard CLI","Advanced Console UIs","标准CLI","标准 CLI"],"readme":"[![Build Status](https://travis-ci.org/lalamove/konfig.svg?branch=master)](https://travis-ci.org/lalamove/konfig)\n[![codecov](https://codecov.io/gh/lalamove/konfig/branch/master/graph/badge.svg)](https://codecov.io/gh/lalamove/konfig)\n[![Go Report Card](https://goreportcard.com/badge/github.com/lalamove/konfig)](https://goreportcard.com/report/github.com/lalamove/konfig)\n[![Go doc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square\n)](https://godoc.org/github.com/lalamove/konfig)\n\n# Konfig\nComposable, observable and performant config handling for Go. Written for larger distributed systems where you may have plenty of configuration sources - it allows you to compose configurations from multiple sources with reload hooks making it simple to build apps that live in a highly dynamic environment.\n\n## What's up with the name?\nThe name is Swedish for \"config\". We have a lot of nationalities here at Lalamove and to celebrate cultural diversity most of our open source packages will carry a name derived from a non-English language that is perhaps spoken by at least one of our employees(?).\n\n# Why another config package?\nMost config packages for Golang are not very extensible and rarely expose interfaces. This makes it complex to build apps which can reload their state dynamically and difficult to mock. Fewer still come with sources such as Vault, Etcd and multiple encoding formats.\nIn short, we didn't find a package that satisfied all of our requirements when we started out.\n\nkonfig is built around 4 small interfaces:\n- Loader\n- Watcher\n- Parser\n- Closer\n\nKonfig features include:\n- **Dynamic** configuration loading\n- **Composable** load configs from multiple sources, such as vault, files and etcd\n- **Polyglot** load configs from multiple format. Konfig supports JSON, YAML, TOML, Key=Value\n- **Fast, Lock-free, Thread safe Read** Reads are **up to 10x faster than Viper**\n- **Observable config - Hot Reload** mechanism and tooling to manage state\n- **Typed Read** get typed values from config or bind a struct\n- **Metrics** exposed prometheus metrics telling you how many times a config is reloaded, if it failed, and how long it takes to reload\n\n# Get started\n```sh\ngo get github.com/lalamove/konfig\n```\n\nLoad and watch a json formatted config file.\n```go\nvar configFiles = []klfile.File{\n\t{\n\t\tPath:   \"./config.json\",\n\t\tParser: kpjson.Parser,\n\t},\n}\n\nfunc init() {\n\tkonfig.Init(konfig.DefaultConfig())\n}\n\nfunc main() {\n\t// load from json file\n\tkonfig.RegisterLoaderWatcher(\n\t\tklfile.New(\u0026klfile.Config{\n\t\t\tFiles: configFiles,\n\t\t\tWatch: true,\n\t\t}),\n\t\t// optionally you can pass config hooks to run when a file is changed\n\t\tfunc(c konfig.Store) error {\n\t\t\treturn nil\n\t\t},\n\t)\n\n\tif err := konfig.LoadWatch(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// retrieve value from config file\n\tkonfig.Bool(\"debug\")\n}\n```\n\n# Store\nThe Store is the base of the config package. It holds and gives access to values stored by keys.\n\n## Creating a Store\nYou can create a global Store by calling `konfig.Init(*konfig.Config)`:\n```go\nkonfig.Init(konfig.DefaultConfig())\n```\nThe global store is accessible directly from the package:\n```go\nkonfig.Get(\"foo\") // calls store.Get(\"foo\")\n```\n\nYou can create a new store by calling `konfig.New(*konfig.Config)`:\n```go\ns := konfig.New(konfig.DefaultConfig())\n```\n\n## Loading and Watching a Store\nAfter registering Loaders and Watchers in the `konfig.Store`, you must load and watch the store.\n\nYou can do both by calling `LoadWatch`:\n```go\nif err := konfig.LoadWatch(); err != nil {\n\tlog.Fatal(err)\n}\n```\n\nYou can call `Load` only, it will load all loaders and return:\n```go\nif err := konfig.Load(); err != nil {\n\tlog.Fatal(err)\n}\n```\n\nAnd finally you can call `Watch` only, it will start all watchers and return:\n```go\nif err := konfig.Watch(); err != nil {\n\tlog.Fatal(err)\n}\n```\n\n\n# Loaders\nLoaders load config values into the store. A loader is an implementation of the loader interface.\n```go\ntype Loader interface {\n\t// Name return the name of the load, it is used to create labeled vectors metrics per loader\n\tName() string\n\t// StopOnFailure indicates if a failure of the loader should trigger a stop\n\tStopOnFailure() bool\n\t// Loads the config and add it to the Store\n\tLoad(Store) error\n\t// MaxRetry returns the maximum number of times to allow retrying on load failure\n\tMaxRetry() int\n\t// RetryDelay returns the delay to wait before retrying\n\tRetryDelay() time.Duration\n}\n```\nYou can register loaders in the config individually or with a watcher.\n\n### Register a loader by itself:\n```go\nconfigLoader := konfig.RegisterLoader(\n\tklfile.New(\n\t\t\u0026klfile.Config{\n\t\t\tFiles: []klfile.File{\n\t\t\t\t{\n\t\t\t\t\tParser: kpjson.Parser,\n\t\t\t\t\tPath:   \"./konfig.json\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t),\n)\n```\n\n### Register a loader with a watcher:\nTo register a loader and a watcher together, you must register a `LoaderWatcher` which is an interface that implements both the `Loader` and the `Watcher` interface.\n```go\nconfigLoader := konfig.RegisterLoaderWatcher(\n\tklfile.New(\n\t\t\u0026klfile.Config{\n\t\t\tFiles: []klfile.File{\n\t\t\t\t{\n\t\t\t\t\tParser: kpjson.Parser,\n\t\t\t\t\tPath:   \"./konfig.json\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tWatch: true,\n\t\t},\n\t),\n)\n```\nYou can also compose a loader and a watcher to create a `LoaderWatcher`:\n```go\nconfigLoader := konfig.RegisterLoaderWatcher(\n\t// it creates a LoaderWatcher from a loader and a watcher\n\tkonfig.NewLoaderWatcher(\n\t\tsomeLoader,\n\t\tsomeWatcher,\n\t),\n)\n```\n\n### Built in loaders\nKonfig already has the following loaders, they all have a built in watcher:\n- [File Loader](loader/klfile/README.md)\n\nLoads configs from files which can be watched. Files can have different parsers to load different formats. It has a built in file watcher which triggers a config reload (running hooks) when files are modified.\n\n- [Vault Loader](loader/klvault/README.md)\n\nLoads configs from vault secrets. It has a built in Poll Watcher which triggers a config reload (running hooks) before the secret and the token from the auth provider expires.\n\n- [HTTP Loader](loader/klhttp/README.md)\n\nLoads configs from HTTP sources. Sources can have different parsers to load different formats. It has a built in Poll Diff Watcher which triggers a config reload (running hooks) if data is different.\n\n- [Etcd Loader](loader/kletcd/README.md)\n\nLoads configs from Etcd keys. Keys can have different parser to load different formats. It has a built in Poll Diff Watcher which triggers a config reload (running hooks) if data is different.\n\n- [Consul loader](loader/klconsul/README.md)\n\nLoads configs from Consul KV. Keys can have different parser to load different formats. It has built in Poll Diff Watcher which triggers a config reload (running hooks) if data is different.\n\n- [ENV Loader](loader/klenv/README.md)\n\nLoads configs from environment variables.\n\n- [Flag Loader](loader/klflag/README.md)\n\nLoads configs from command line flags.\n\n- [io.Reader Loader](loader/klreader/README.md)\n\nLoads configs from an io.Reader.\n\n\n### Parsers\nParsers parse an `io.Reader` into a `konfig.Store`. These are used by some loaders to parse the data they fetch into the config store. The File Loader, Etcd Loader and HTTP Loader use Parsers.\n\nConfig already has the following parsers:\n- [JSON Parser](parser/kpjson/README.md)\n- [TOML Parser](parser/kptoml/README.md)\n- [YAML Parser](parser/kpyaml/README.md)\n- [KV Parser](parser/kpkeyval/README.md)\n- [Map Parser](parser/kpmap/README.md)\n\n# Watchers\nWatchers trigger a call on a Loader on events. A watcher is an implementation of the `Watcher` interface.\n```go\ntype Watcher interface {\n\t// Start starts the watcher, it must not be blocking.\n\tStart() error\n\t// Done indicate whether the watcher is done or not\n\tDone() \u003c-chan struct{}\n\t// Watch should block until an event unlocks it\n\tWatch() \u003c-chan struct{}\n\t// Close closes the watcher, it returns a non nil error if it is already closed\n\t// or something prevents it from closing properly.\n\tClose() error\n\t// Err returns the error attached to the watcher\n\tErr() error\n}\n```\n\n### Built in watchers\nKonfig already has the following watchers:\n- [File Watcher](watcher/kwfile/README.md)\n\nWatches files for changes.\n\n- [Poll Watcher](watcher/kwpoll/README.md)\n\nSends events at a given rate, or if diff is enabled. It takes a Getter and fetches the data at a given rate. If data is different, it sends an event.\n\n# Hooks\nHooks are functions ran after a successful loader `Load()` call. They are used to reload the state of the application on a config change.\n\n### Registering a loader with some hooks\nYou can register a loader or a loader watcher with hooks.\n```go\nconfigLoader := konfig.RegisterLoaderWatcher(\n\tklfile.New(\n\t\t\u0026klfile.Config{\n\t\t\tFiles: []klfile.File{\n\t\t\t\t{\n\t\t\t\t\tParser: kpyaml.Parser,\n\t\t\t\t\tPath:   \"./konfig.yaml\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tWatch: true,\n\t\t},\n\t),\n\tfunc(s konfig.Store) error {\n\t\t// Here you should reload the state of your app\n\t\treturn nil\n\t},\n)\n```\n\n### Adding hooks to an existing loader\nYou can register a *Loader* or a *LoaderWatcher* with hooks.\n```go\nconfigLoader.AddHooks(\n\tfunc(s konfig.Store) error {\n\t\t// Here you should reload the state of your app\n\t\treturn nil\n\t},\n\tfunc(s konfig.Store) error {\n\t\t// Here you should reload the state of your app\n\t\treturn nil\n\t},\n)\n```\n\n### Adding hooks on keys\nAlternatively, you can add hooks on keys. Hooks on keys will match for prefix in order to run a hook when any key with a given prefix is updated.\nA hook can only be run once per load event, even if multiple keys match that hook.\n```go\nkonfig.RegisterKeyHook(\n\t\"db.\",\n\tfunc(s konfig.Store) error {\n\t\treturn nil\n\t},\n)\n```\n\n# Closers\n*Closers* can be added to konfig so that if konfig fails to load, it will execute `Close()` on the registered *Closers*.\n```go\ntype Closer interface {\n\tClose() error\n}\n```\n\n## Register a Closer\n```go\nkonfig.RegisterCloser(closer)\n```\n\n# Config Groups\nYou can namespace your configs using config Groups.\n```go\nkonfig.Group(\"db\").RegisterLoaderWatcher(\n\tklfile.New(\n\t\t\u0026klfile.Config{\n\t\t\tFiles: []klfile.File{\n\t\t\t\t{\n\t\t\t\t\tParser: kpyaml.Parser,\n\t\t\t\t\tPath:   \"./db.yaml\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tWatch: true,\n\t\t},\n\t),\n)\n\n// accessing grouped config\ndbHost := konfig.Group(\"db\").MustString(\"credentials.host\")\n```\n\n# Binding a Type to a Store\nYou can bind a type to the konfig store if you want your config values to be unmarshaled to a **struct** or a **map[string]interface{}**. Then you can access an instance of that type in a thread safe manner (in order to be safe for dynamic config updates).\n\nLet's see with an example of a json config file:\n```json\n{\n    \"addr\": \":8080\",\n    \"debug\": true,\n    \"db\": {\n        \"username\": \"foo\"\n    },\n    \"redis\": {\n        \"host\": \"127.0.0.1\"\n    }\n}\n```\n\n```go\ntype DBConfig struct {\n\tUsername string\n}\ntype Config struct {\n\tAddr      string\n\tDebug     string\n\tDB        DBConfig `konfig:\"db\"`\n\tRedisHost string   `konfig:\"redis.host\"`\n}\n\n// we init the root konfig store\nkonfig.Init(konfig.DefaultConfig())\n\n// we bind the Config struct to the konfig.Store\nkonfig.Bind(Config{})\n\n// we register our config file\nkonfig.RegisterLoaderWatcher(\n\tklfile.New(\n\t\t\u0026klfile.Config{\n\t\t\tFiles: []klfile.File{\n\t\t\t\t{\n\t\t\t\t\tParser: kpjson.Parser,\n\t\t\t\t\tPath:   \"./config.json\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tWatch: true,\n\t\t},\n\t),\n)\n\n// we load our config and start watching\nif err := konfig.LoadWatch(); err != nil {\n\tlog.Fatal(err)\n}\n\n// Get our config value\nc := konfig.Value().(Config)\n\nfmt.Println(c.Addr) // :8080\n```\n\nNote that you can compose your config sources. For example, have your credentials come from Vault and be renewed often and have the rest of your config loaded from a file and be updated on file change.\n\n**It is important to understand how Konfig unmarshals your config values into your struct.**\nWhen a Loader calls *konfig.Set()*, if the konfig store has a value bound to it, it will try to unmarshal the key to the bound value.\n- First, it will look for field tags in the struct, if a tag matches exactly the key, it will unmarshal the key to the struct field.\n- Then, it will do a EqualFold on the field name and the key, if they match, it will unmarshal the key to the struct field.\n- Then, if the key has a dot, it will check if the tag or the field name (to lowercase) is a prefix of the key, if yes, it will check if the type of the field is a struct of pointer, if yes, it will check the struct using what's after the prefix as the key.\n\n\n# Read from config\nApart from reading from the bound config value, konfig provides several methods to read values.\n\nEvery method to retrieve config values come in 2 flavours:\n- **Get** reads a value at the given key. If key is not present it returns the zero value of the type.\n- **MustGet**  reads a value at the given key. If key is not present it panics.\n\nAll methods to read values from a Store:\n```go\n// Exists checks whether the key k is set in the store.\nExists(k string) bool\n\n// Get gets the value with the key k from the store. If the key is not set, Get returns nil. To check whether a value is really set, use Exists.\nGet(k string) interface{}\n// MustGet tries to get the value with the key k from the store. If the key k does not exist in the store, MustGet panics.\nMustGet(k string) interface{}\n\n// MustString tries to get the value with the key k from the store and casts it to a string. If the key k does not exist in the store, MustString panics.\nMustString(k string) string\n// String tries to get the value with the key k from the store and casts it to a string. If the key k does not exist it returns the Zero value.\nString(k string) string\n\n// MustInt tries to get the value with the key k from the store and casts it to a int. If the key k does not exist in the store, MustInt panics.\nMustInt(k string) int\n// Int tries to get the value with the key k from the store and casts it to a int. If the key k does not exist it returns the Zero value.\nInt(k string) int\n\n// MustFloat tries to get the value with the key k from the store and casts it to a float. If the key k does not exist in the store, MustFloat panics.\nMustFloat(k string) float64\n// Float tries to get the value with the key k from the store and casts it to a float. If the key k does not exist it returns the Zero value.\nFloat(k string) float64\n\n// MustBool tries to get the value with the key k from the store and casts it to a bool. If the key k does not exist in the store, MustBool panics.\nMustBool(k string) bool\n// Bool tries to get the value with the key k from the store and casts it to a bool. If the key k does not exist it returns the Zero value.\nBool(k string) bool\n\n// MustDuration tries to get the value with the key k from the store and casts it to a time.Duration. If the key k does not exist in the store, MustDuration panics.\nMustDuration(k string) time.Duration\n// Duration tries to get the value with the key k from the store and casts it to a time.Duration. If the key k does not exist it returns the Zero value.\nDuration(k string) time.Duration\n\n// MustTime tries to get the value with the key k from the store and casts it to a time.Time. If the key k does not exist in the store, MustTime panics.\nMustTime(k string) time.Time\n// Time tries to get the value with the key k from the store and casts it to a time.Time. If the key k does not exist it returns the Zero value.\nTime(k string) time.Time\n\n// MustStringSlice tries to get the value with the key k from the store and casts it to a []string. If the key k does not exist in the store, MustStringSlice panics.\nMustStringSlice(k string) []string\n// StringSlice tries to get the value with the key k from the store and casts it to a []string. If the key k does not exist it returns the Zero value.\nStringSlice(k string) []string\n\n// MustIntSlice tries to get the value with the key k from the store and casts it to a []int. If the key k does not exist in the store, MustIntSlice panics.\nMustIntSlice(k string) []int\n// IntSlice tries to get the value with the key k from the store and casts it to a []int. If the key k does not exist it returns the Zero value.\nIntSlice(k string) []int\n\n// MustStringMap tries to get the value with the key k from the store and casts it to a map[string]interface{}. If the key k does not exist in the store, MustStringMap panics.\nMustStringMap(k string) map[string]interface{}\n// StringMap tries to get the value with the key k from the store and casts it to a map[string]interface{}. If the key k does not exist it returns the Zero value.\nStringMap(k string) map[string]interface{}\n\n// MustStringMapString tries to get the value with the key k from the store and casts it to a map[string]string. If the key k does not exist in the store, MustStringMapString panics.\nMustStringMapString(k string) map[string]string\n// StringMapString tries to get the value with the key k from the store and casts it to a map[string]string. If the key k does not exist it returns the Zero value.\nStringMapString(k string) map[string]string\n```\n\n# Strict Keys\nYou can define required keys on the `konfig.Store` by calling the `Strict` method. When calling strict method, konfig will set required keys on the store and during the first `Load` call on the store it will check if the keys are present, if not, Load will return a non nil error. Then, after every `Load` on a loader, konfig will check again if the keys are still present, if not, the loader `Load` will be considered a failure.\n\nUsage:\n```\n// We init the root konfig store\nkonfig.Init(konfig.DefaultConfig()).Strict(\"debug\", \"username\")\n\n// Register our loaders\n...\n\n// We load our config and start watching.\n// If strict keys are not found after the load operation, LoadWatch will return a non nil error.\nif err := konfig.LoadWatch(); err != nil {\n\tlog.Fatal(err)\n}\n```\n\nAlternatively, `BindStructStrict` can be used to strictly bind config.\nUsage:\n```\ntype DBConfig struct {\n\tUsername string\n}\ntype Config struct {\n\tAddr      string    `konfig:\"-\"` // this key will be non-strict\n\tDB        DBConfig  `konfig:\"db\"`\n\tRedisHost string    `konfig:\"redis.host\"`\n}\n\n// we init the root konfig store\nkonfig.Init(konfig.DefaultConfig())\n\n// we bind the Config struct to the konfig.Store\nkonfig.BindStructStrict(Config{})\n\n// Register our loaders\n...\n\n// We load our config and start watching.\n// If any strict key is not found after the load operation, LoadWatch will return a non nil error.\nif err := konfig.LoadWatch(); err != nil {\n\tlog.Fatal(err)\n}\n```\n\n# Getter\nTo easily build services which can use dynamically loaded configs you can create getters for specific keys. A getter implements `ngetter.GetterTyped` from [nui](github.com/lalamove/nui) package. It is useful when building apps in larger distributed environments.\n\nExample with a config value set for the debug key:\n```go\ndebug := konfig.Getter(\"debug\")\n\ndebug.Bool() // true\n```\n\n# Metrics\nKonfig comes with prometheus metrics.\n\nTwo metrics are exposed:\n- Config reloads counter vector with labels\n- Config reload duration summary vector with labels\n\nExample of metrics:\n```\n# HELP konfig_loader_reload Number of config loader reload\n# TYPE konfig_loader_reload counter\nkonfig_loader_reload{loader=\"config-files\",result=\"failure\",store=\"root\"} 0.0\nkonfig_loader_reload{loader=\"config-files\",result=\"success\",store=\"root\"} 1.0\n\n# HELP konfig_loader_reload_duration Histogram for the config reload duration\n# TYPE konfig_loader_reload_duration summary\nkonfig_loader_reload_duration{loader=\"config-files\",store=\"root\",quantile=\"0.5\"} 0.001227641\nkonfig_loader_reload_duration{loader=\"config-files\",store=\"root\",quantile=\"0.9\"} 0.001227641\nkonfig_loader_reload_duration{loader=\"config-files\",store=\"root\",quantile=\"0.99\"} 0.001227641\nkonfig_loader_reload_duration_sum{loader=\"config-files\",store=\"\"} 0.001227641\nkonfig_loader_reload_duration_count{loader=\"config-files\",store=\"\"} 1.0\n```\n\nTo enable metrics, you must pass a custom config when creating a config store:\n```go\nkonfig.Init(\u0026konfig.Config{\n\tMetrics: true,\n\tName: \"root\",\n})\n```\n\n# Benchmark\nBenchmarks are run on `viper`, `go-config` and `konfig`. Benchmark are done on reading ops and show that Konfig is 0 allocs on read and at leat 3x faster than Viper:\n```\ncd benchmarks \u0026\u0026 go test -bench . \u0026\u0026 cd ../\ngoos: linux\ngoarch: amd64\npkg: github.com/lalamove/konfig/benchmarks\nBenchmarkGetKonfig-4            200000000                7.75 ns/op            0 B/op          0 allocs/op\nBenchmarkStringKonfig-4         30000000                49.9 ns/op             0 B/op          0 allocs/op\nBenchmarkGetViper-4             20000000               101 ns/op              32 B/op          2 allocs/op\nBenchmarkStringViper-4          10000000               152 ns/op              32 B/op          2 allocs/op\nBenchmarkGetGoConfig-4          10000000               118 ns/op              40 B/op          3 allocs/op\nBenchmarkStringGoConfig-4       10000000               125 ns/op              40 B/op          3 allocs/op\nPASS\n```\n\n\n# Contributing\n\nContributions are welcome. To make contributions, fork the repository, create a branch and submit a Pull Request to the master branch.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flalamove%2Fkonfig","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flalamove%2Fkonfig","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flalamove%2Fkonfig/lists"}