{"id":19065744,"url":"https://github.com/last9/last9-cdk","last_synced_at":"2025-04-28T12:22:35.708Z","repository":{"id":39712210,"uuid":"420964107","full_name":"last9/last9-cdk","owner":"last9","description":"Last9 CDK","archived":false,"fork":false,"pushed_at":"2024-09-03T07:36:53.000Z","size":1133,"stargazers_count":9,"open_issues_count":7,"forks_count":0,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-04-18T15:17:36.049Z","etag":null,"topics":["observability","prometheus","prometheus-metrics","python","sre"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/last9.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-10-25T09:44:29.000Z","updated_at":"2022-12-30T05:00:25.000Z","dependencies_parsed_at":"2023-12-24T00:32:02.809Z","dependency_job_id":"a1219be7-f914-4668-aaba-39c9603ddde5","html_url":"https://github.com/last9/last9-cdk","commit_stats":{"total_commits":78,"total_committers":11,"mean_commits":7.090909090909091,"dds":0.7435897435897436,"last_synced_commit":"94e7143270637141323e4f9661f2db091229ea71"},"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/last9%2Flast9-cdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/last9%2Flast9-cdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/last9%2Flast9-cdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/last9%2Flast9-cdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/last9","download_url":"https://codeload.github.com/last9/last9-cdk/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251311607,"owners_count":21569064,"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":["observability","prometheus","prometheus-metrics","python","sre"],"created_at":"2024-11-09T00:52:24.152Z","updated_at":"2025-04-28T12:22:35.692Z","avatar_url":"https://github.com/last9.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Last9 CDK for HTTP\n\nLast9 Golang CDK is a way to emit commonly aggregated metrics straight from your Golang application that serves HTTP traffic.\n\n### Why?\n\nTraditional metrics emitters emit, per-path or handler-based metrics. Both these approaches have their drawbacks.\n\n- Per-path-based metrics can explode the cardinality because `/user/foo` `/user/bar` and so on can result in millions of endpoints.\n- handler-based metrics can absorb details of the route details.\n\n### What metrics does CDK emit?\n\n### RED Metrics\n\nThe very first metrics to be emitted are Rate, Error, and Duration.\n\n```go\nhttpRequestsDuration = prometheus.NewHistogramVec(\n\t\tprometheus.HistogramOpts{\n\t\t\tName: \"http_requests_duration_milliseconds\",\n\t\t\tHelp: \"HTTP requests duration per path\",\n\t\t},\n\t\t[]string{\n\t\t\t\"per\", \"hostname\", \"domain\", \"method\", \"program\", \"status\",\n\t\t\t\"tenant\", \"cluster\",\n\t\t},\n\t)\n```\n\n## Labels Explained\n\n| Name | Description |\n| --- | --- |\n| hostname | current Hostname where the metric is emitted from |\n| program | Binary / Process name where the metric is emitted from |\n| per | This is the \"main\" label, which contains the pathname or an identifier that you emit per request.\nBy default, it is the path pattern if the Mux is one of the supported List and the entire URL Path where Mux does not have a path parameter.\nOptionally, this can ALSO be a custom string (Read below for details) |\n| domain | domain at which the request was received |\n| method | HTTP method |\n| status | HTTP Status returned |\n| tenant | Optional Field for a multi-tenant application |\n| cluster | Optional Field for a multi-cluster deployment |\n\n---\n\n## Say more about Tenancy\n\nMost modern SaaS applications end up being multi-tenant or multi-clustered where it's crucial to identify the behavior across each, separately.\n\nCDK honors this need as a first-class property and has reserved two label fields for this purpose. These two are:\n\n- tenant\n- cluster\n\n**Features**\n\n- Simple configuration for multi-tenancy or multi-cluster using an additional label.\n- Cross-tenant aggregation or segregation later via PromQL.\n- Allow data with no tenant or cluster information to be written or queried.\n\n---\n\n### How are metrics exposed?\n\nCDK emits metrics in ~~Openmetrics~~ Prometheus Exposition format. The metric port and endpoint expose the freshest metrics to be pulled by a Prometheus.\n\nYou can read more about the Prometheus exposition format on the [link](https://github.com/Showmax/prometheus-docs/blob/master/content/docs/instrumenting/exposition_formats.md)\n\n### Golang Support\n\nThere may be parts to the CDK which are activated ONLY when supported frameworks are detected or declared.\n\n```go\nimport (\n\t\"github.com/last9/last9-cdk/go/httpmetrics\"\n)\n\n// given you have a http.Handler or a Router already\n// Only line that needs to be changed\nhttpmetrics.REDHandler(handler)\n```\n\n| Name | Supported | Mux Supported |\n| --- | --- | --- |\n| http.ServeMux | Yes | Yes |\n| Gorilla | Yes | Yes |\n| Pat | Yes | Yes |\n| Chi | Yes | Yes |\n\n---\n\n## An Example Golang Application\n\ngo get the httpmetrics\n\n```bash\ngo get -v github.com/last9/last9-cdk/go/httpmetrics\n```\n\nAssuming a basic main.go\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\n\t\"github.com/last9/last9-cdk/go/httpmetrics\"\n)\n\nfunc handler(w http.ResponseWriter, r *http.Request) {\n\tfmt.Fprintf(w, \"Hi there, I love %s!\", r.URL.Path[1:])\n}\n\nfunc main() {\n\thttp.Handle(\"/golang\", httpmetrics.REDHandler(http.HandlerFunc(handler)))\n\tgo httpmetrics.ServeMetrics(9091)\n\tlog.Fatal(http.ListenAndServe(\":9090\", nil))\n}\n```\n\nOnce set up, you can send a couple of test requests on your application\n\n```bash\n➜  example curl -XPOST http://localhost:9090/golang -d '{}'\nHi there, I love golang!%                                                                                                                                                        ➜  example curl -XGET http://localhost:9090/golang -d '{}' \nHi there, I love mount!\n```\n\n\u003e Visit [localhost:9091/metrics](http://localhost:9091/metrics) to see your RED metrics\n\u003e \n\n---\n\n## Custom Labels\n\nIf you want to change the default path being emitted, it is extremely easy to do so.\n\nSay, my application handler is something like this\n\n```go\nm := mux.NewRouter()\nm.Handle(\"/api/category/{category}/item/{id}\", itemHandler())\nm.Use(REDHandler)\n```\n\nThis will emit metrics where the `per` label will look like `/api/category/{category}/item/{id}`\n\nBut you want the category to NOT be abstracted. For situations like these, you can use the \n\n`REDHandlerWithLabelMaker` function to assist the label-making process.\n\n```go\n// REDHandlerWithLabelMaker accepts a function that in-turn accepts\n// both the request and the mux.\nm.Use(REDHandlerWithLabelMaker(\n\tfunc(r *http.Request, m http.Handler) map[string]string {\n\n\t\t// Gorilla exposes the variables using a request local mux\n\t\tvars := mux.Vars(r)\n\t\tcategory := vars[\"category\"]\n\n\t\treturn map[string]string{\n\t\t\t\"per\":     strings.Replace(r.URL.Path, \"{category}\", category),\n\t\t\t\"tenant\":  \"possible_override\", // You may also override other labels\n\t\t}\n\n\t},\n))\n```\n\nVoila! That's it\n\n\u003e The above example is for Gorilla Mux, but it's extremely straightforward to draw inspiration for other mux like Pat, etc.\n\u003e \n\n---\n\n# About Last9\n\nThis project is sponsored and maintained by [Last9](https://last9.io). Last9 builds reliability tools for SRE and DevOps.\n\n\u003ca href=\"https://last9.io\"\u003e\u003cimg src=\"https://last9.github.io/assets/email-logo-green.png\" alt=\"\" loading=\"lazy\" height=\"40px\" /\u003e\u003c/a\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flast9%2Flast9-cdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flast9%2Flast9-cdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flast9%2Flast9-cdk/lists"}