{"id":28551828,"url":"https://github.com/heroku/plexy","last_synced_at":"2025-07-03T17:30:37.924Z","repository":{"id":12900815,"uuid":"73087337","full_name":"heroku/plexy","owner":"heroku","description":"A toolkit for building excellent APIs with Elixir","archived":false,"fork":false,"pushed_at":"2024-08-15T18:38:19.000Z","size":129,"stargazers_count":161,"open_issues_count":7,"forks_count":4,"subscribers_count":88,"default_branch":"main","last_synced_at":"2025-06-08T22:08:40.313Z","etag":null,"topics":["elixir","plug"],"latest_commit_sha":null,"homepage":null,"language":"Elixir","has_issues":false,"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/heroku.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2016-11-07T14:39:24.000Z","updated_at":"2024-12-30T00:18:52.000Z","dependencies_parsed_at":"2024-01-24T23:46:17.494Z","dependency_job_id":null,"html_url":"https://github.com/heroku/plexy","commit_stats":{"total_commits":89,"total_committers":14,"mean_commits":6.357142857142857,"dds":0.6629213483146068,"last_synced_commit":"bfdd4a539c2c0d8f80101cca8061a945a2cd6159"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/heroku/plexy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heroku%2Fplexy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heroku%2Fplexy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heroku%2Fplexy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heroku%2Fplexy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/heroku","download_url":"https://codeload.github.com/heroku/plexy/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heroku%2Fplexy/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263369389,"owners_count":23456293,"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":["elixir","plug"],"created_at":"2025-06-10T04:07:08.295Z","updated_at":"2025-07-03T17:30:37.918Z","avatar_url":"https://github.com/heroku.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Plexy\n\n[![Build Status](https://travis-ci.org/heroku/plexy.svg?branch=master)](https://travis-ci.org/heroku/plexy)\n[![Module Version](https://img.shields.io/hexpm/v/plexy.svg)](https://hex.pm/packages/plexy)\n[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/plexy/)\n[![Total Download](https://img.shields.io/hexpm/dt/plexy.svg)](https://hex.pm/packages/plexy)\n[![License](https://img.shields.io/hexpm/l/plexy.svg)](https://github.com/heroku/plexy/blob/master/LICENSE.md)\n[![Last Updated](https://img.shields.io/github/last-commit/heroku/plexy.svg)](https://github.com/heroku/plexy/commits/master)\n\n[Interagent](https://github.com/interagent)-compatible web services in Elixir, inspired by [Pliny](https://github.com/interagent/pliny).\n\nPlexy helps developers write and maintain excellent APIs. It integrates well with other services by implementing logs-as-data, request/response instrumentation, request IDs, and encourages good defaults for logging and exception reporting.\n\nNotably, Plexy is not a framework or a set of code generators. You can use Plexy's plugs with an existing Phoenix or even a vanilla Elixir web app. Plexy will work with you, not dictate how you work. It is also database agnostic.\n\n## Installation\n\nAdd `:plexy` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:plexy, \"~\u003e 0.3.0\"}\n  ]\nend\n```\n\nRequire Plexy plugs in your application:\n\n```elixir\ndefmodule MyRouter do\n  use Plug.Router\n\n  plug Plexy.RequestId\n  plug Plexy.Instrumentor\n  plug :match\n  plug :dispatch\nend\n```\n\nCall the `Plexy.Logger` wherever you wish to log information:\n\n```elixir\nPlexy.Logger.count(:http_clients_404, 1)\n```\n\n## Usage\n\nImport the plugs into your Plug router or your Phoenix router pipeline as necessary. You can also import the plugs into your own plugs, as needed.\n\n## Concepts\n\n### Logs-as-data\n\nBy publishing consistent and machine-interpretable logs, we are able to monitor our applications and trace requests with tools like Librato and splunk.\n\nIt is useful to think of logs as data, instead of just as strings. For logs-as-data, we want to publish logs like\n\n```\nflag1 key1=val1 key2=val2 flag2\n```\n\nThis is known as an [`l2met`](https://github.com/ryandotsmith/l2met) compatible log format. Outputting logs in this format will allow integrations like Librato to easily show graphs of this data, and allows us to search Splunk for matching loglines.\n\nTo output arbitrary data to the log, you can pass a hash to `Plexy.Logger` and it will format it correctly. For example:\n\n```elixir\nPlexy.Logger.info(test: true, foo: \"bar\")\n```\n\nbecomes\n\n```\n21:02:24.882 request_id=fc06cbd2-b8b6-4257-801d-89253ed83962  test=true foo=bar\n```\n\nYou can pass in maps, structs and keyword lists to `Plexy.Logger` methods, and it will log them correctly without asking you to worry about converting them to strings.\n\n### Request IDs\n\nRequest IDs are a unique identifier associated with every single request your Plexy app receives. Importing the `Plexy.RequestId` plug will append the request ID to the connection and also make it available in the Logger metadata.\n\nRequest IDs are read on the request coming in to the app, so if you are on a platform like Heroku that adds Request ID headers, Plexy can read the existing Request ID(s) and append its own Request ID to the list. By default, it reads out of the list of headers `[\"Request-Id\", \"X-Request-Id\"]` for incoming requests, but you can configure this list by setting `req_headers` in `Config`.\n\nSimilarly, it defaults to setting a `Request-Id` header on the response so that you can easily trace requests. To customize this header, set `res_header` in `Config`.\n\n### Request/response instrumentation\n\nBy default, Plexy will instrument each request and output the timing information in loglines. This, in addition with a request ID, makes it possible to splunk for a particular request and see information about its performance, HTTP response code, etc. An example request / response might look like this in logs:\n\n```\n21:02:24.882 request_id=fc06cbd2-b8b6-4257-801d-89253ed83962  at=start instrumentation=true method=GET path=/hello\n21:02:24.884 request_id=fc06cbd2-b8b6-4257-801d-89253ed83962  \u003cmetrics\u003e\n21:02:24.884 request_id=fc06cbd2-b8b6-4257-801d-89253ed83962  at=finish instrumentation=true method=GET path=/hello elapsed=1.387 status=200\n```\n\n### Metrics\n\nPlexy provides some helper functions for taking metric measurements and outputting those metrics to the logs.\n\n#### Counts\n\n`Plexy.Logger.count(metric_name, count)` will log the given metric as a count for easy graphing in Librato.\n\n#### Measures\n\n`Plexy.Logger.measure(metric_name, 123)` expects a time in milliseconds and logs it as the given metric name.\n\n`Plexy.Logger.measure(metric_name, func)` will measure the amount of time in milliseconds required to run the given function and logs it as the given metric name. This also returns the value of the given function. It also adds `.ms` to the metric in case you forget.\n\n```\nPlexy.Logger.measure(:fast, func)\n# logs =\u003e measure#plexy.fast.ms=123\n\nPlexy.Logger.measure(\"fast.ms\", func)\n# logs =\u003e measure#my_app.fast.ms=123\n```\n\n### Configuring logging\n\nYou may need to configure your logging slightly differently, but in general, this pattern will help you create l2met-compatible loglines with UTC times and the Request ID on each line. In `config/config.exs`:\n\n```elixir\nconfig :logger,\n  utc_log: true\n\nconfig :logger, :console,\n  format: \"$time $metadata $message\\n\",\n  metadata: [:request_id]\n```\n\nMake sure that the `Plexy.RequestId` plug is included in your Elixir app per the Installation instructions, and you will have the `request_id` in the log metadata.\n\nBy default Plexy looks for an ENV variable named `APP_NAME` and will then fall back to `plexy` if it is not provided. This value is used for metrics logging.\n\n```\nPlexy.Logger.measure(:fast, 123)\n# logs =\u003e measure#plexy.fast=123\n\n# APP_NAME env set to \"my_app\"\nPlexy.Logger.measure(:fast, 123)\n# logs =\u003e measure#my_app.fast=123\n```\n\nIf you would like to use a different env var for the app name you can set it.\n\n```\n# config/config.exs\nconfig :plexy,\n  app_name: {:system, \"HEROKU_APP_NAME\"}\n\n# HEROKU_APP_NAME env set to \"much_amazing\"\nPlexy.Logger.measure(:fast, 123)\n# logs =\u003e measure#much_amazing.fast=123\n\n# or with a default\n# config/config.exs\nconfig, :plexy,\n  app_name: {:system, \"HEROKU_APP_NAME\", \"such_wow\"}\n\n# HEROKU_APP_NAME env is not set\nPlexy.Logger.measure(:fast, 123)\n# logs =\u003e measure#such_wow.fast=123\n```\n\n### Configuring exception reporting\n\nWe recommend [Rollbax](https://github.com/elixir-addicts/rollbax) as it lives in your logging backends pipeline. This means that the params retracted by the Plexy Redactor won't ever show up in Rollbar. To hide certain keys from your Rollbar reporting, see the next section.\n\n### Protecting secrets from appearing in logs or Rollbar\n\nA common pain point for production systems can be inadvertent leaks of secrets to log lines or to exception reporters like Rollbar. While each app will have different values that it considers secret, and how much about a customer or end-user can be logged will depend on the industry, we have provided a generic way to redact certain keys from appearing in logs or being passed through the logging backend pipeline to Rollbax.\n\nTo use it, add this to `config/config.exs`:\n\n```elixir\n config :plexy, :logger,\n   redactors: [\n     {Plexy.Logger.SimpleRedactor, [\n       redact: [\"username\"],\n       filter: [\"password\"]\n     ]}\n   ]\n```\n\nYou can also write your own Redactor module and configure it here. The Redactor runs before the data is passed to each logging backend in `Plexy.Logger`.\n\nKeys that appear in the `redact` list will appear with the value `REDACTED` in logs. Keys that appear in the filter list will cause the entire logline to be redacted from the record. Examples:\n\n```elixir\niex\u003e SimpleRedactor.run(\"username=bob\", redact: [\"username\"])\n{:cont, \"username=REDACTED\"}\niex\u003e SimpleRedactor.run(\"password=mysecred\", filter: [\"password\"])\n{:cont, \"\"}\n```\n\n## Copyright and License\n\nCreated at Heroku by:\n\n- [@Adovenmuehle](https://github.com/Adovenmuehle)\n- [@blackfist](https://github.com/blackfist)\n- [@joshwlewis](https://github.com/joshwlewis)\n- [@kennyp](https://github.com/kennyp)\n- [@mathias](https://github.com/mathias)\n\nCopyright (c) 2019 maxbeizer\n\nThis work is free. You can redistribute it and/or modify it under the\nterms of the MIT License. See the [LICENSE.md](./LICENSE.md) file for more details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fheroku%2Fplexy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fheroku%2Fplexy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fheroku%2Fplexy/lists"}