{"action":"created","comment":{"url":"https://api.github.com/repos/verypossible/engineering-blog/pulls/comments/179925872","pull_request_review_id":110264986,"id":179925872,"diff_hunk":"@@ -0,0 +1,200 @@\n+# Thoughtful Logging in Elixir: A Phoenix Story\n+\n+The choices you make at foundational points of your architecture can have massive implications down the road for your Elixir applications. By taking an informed approach to logging, you can set yourself up for success.\n+\n+--\n+\n+Well thought out logging is a crucial component to a successful system. It is an invaluable tool for developers with uses including tracking down faults, observing emergent patterns, identifying misuse of the system, and indicating what is important or not to the system. However, it is not always obvious what information should be logged, at what level, and at what point to log it. Often times a baseline of quality is set early on in a project and it becomes increasingly difficult to course correct as time goes on. Lets observe this through the lens of creating a Phoenix API, and discuss some alternatives to common pitfalls encountered along the way.\n+\n+\n+- Elixir 1.6.4\n+- Phoenix 1.3.2\n+\n+\n+It is 10:13 AM, your stomach flutters with anticipation, the only noise is the enter key bottoming out as you finish typing `mix phx.new machamp --no-ecto`:\n+\n+```\n+Fetch and install dependencies? [Yn] y\n+```\n+\n+Enter the directory, start your server with `mix phx.server`, and navigate your browser to `0.0.0.0:4000?arms=four`. A quick glance at the terminal shows:\n+\n+```elixir\n+[info] GET / [debug] Processing with MachampWeb.PageController.index/2\n+  Parameters: %{\"arms\" => \"four\"}\n+  Pipelines: [:browser]\n+[info] Sent 200 in 164µs\n+```\n+\n+Phoenix logs helpful information about the route hit, the associated controller, action, and parameters, and the response time. This provides a clear picture of what sort of requests are being made against your system. If any parameters are deemed sensitive and should not be logged this can be configured like so:\n+\n+```elixir\n+config :phoenix, :filter_parameters, [\"password\", \"secret\"]\n+```\n+\n+Phoenix’s default is just `[\"password\"]`. So far this is straightforward, but lets see what this looks like in production. Enter the assets directory and run `npm install && npm run deploy`. Hop back up to the project root and run `mix phx.digest` to finish the ceremony required to serve static assets without error. Finally, restart your server this time overriding the mix environment and providing a port with `MIX_ENV=prod PORT=4000 mix phx.server` and hit it with your browser again:\n+\n+```\n+17:42:51.469 [info] GET /\n+17:42:51.469 [info] Sent 200 in 167µs\n+```\n+\n+Aweso… wait, where did the log with parameters go? The issue here is that by default when Phoenix logs requests they are logged at the debug level which is fine in development but falls apart in production. It may not seem like it yet, but this is a fork in the road. It is as much an opportunity to take a measured approach as it is to chop your foot off with an axe.\n+\n+# Don’t Use an Axe\n+\n+Often developers are tempted here to simply reconfigure Logger’s `:level` option in `config/prod.exs` like so:\n+\n+```elixir\n+config :logger, level: :debug\n+```\n+\n+However, this will end up hurting you in the long run more than helping as it undermines the semantics of log levels. You typically would not want all of your debug level calls to oversaturate your production logs all the time. It is preferable to log information you wish to see consistently at the info level or higher and reserve debug level logs for the nitty gritty details you only want to see when the circumstances demand it.\n+\n+If you take an everything-always approach to logging, it won't be long before the lines of what is and isn't important blur. Time will roll forward and your team will write debug  logs throughout your system that may not be intended to be seen in production. Developers will start leaning on the idea that anything goes and put less thought into logging as a whole. The degradation of quality compounds over time.\n+\n+# Why it Matters\n+\n+Who cares? So what if I log every other line in production at random levels? Isn't it better to have more information than less? Future developers of your system will care, including yourself. Logs should always serve a purpose, and long levels are named aptly to convey what purpose they serve. As such, debug logs are useful... when debugging. It may not always be that you are chasing a bug down, it may be for understanding what is going on at a deeper level or for some other exceptional reason. The fact of the matter is debug logs tend to be one or many of the following: verbose, irrelevant to regular operation of the system, expensive to attempt because they are within tight code, or expensive to even compute.\n+\n+Over-logging is especially relevant in Elixir due to Logger's backpressure feature. Typically Logger runs in async mode. When you attempt to log it happens asynchronously, allowing you to continue on while it works. However, if Logger gets overwhelmed it switches to sync mode causing all log requests to block until completion, only returning to async mode once it recovers from the load. This is a fun way to find other architectural flaws in your system's design as it will test how resilient you are to timeouts in a very chaotic fashion. This is not fun to encounter in production.\n+\n+Even if you do not overwhelm Logger itself, it is still possible to chew through a good portion of your available CPU by fully printing large data structures or calculating non-trivial values within logs. Another consequence to consider is increased volume of logs which impacts the cost and speed of storage, transportation, and analytics. Perhaps worst of all is the cost incurred from not knowing what is important or not. Have you ever been introduced to a system, looked at the logs with someone, and had them say something along the lines of \"oh just ignore that, that doesn't matter anymore\"? _Then why are you logging it?_\n+\n+# Tune Out the Pain\n+\n+Logger ships with a variety of options to mitigate some of the more common issues people encounter. To adjust when Logger considers itself overwhelmed with messages, use the `:sync_threshold` option, this is the number of queued messages above which Logger will switch to sync mode. It will return to async mode when the number of queued messages drops below `:sync_threshold * 0.75`. In a similar vein, `:discard_threshold` is the level at which Logger will stop queueing new messages altogether and instead discard them to protect against memory use spiraling out of control.\n+\n+The `:level` option allows you to suppress what level of logs are printed, and even lets you change this value at runtime. This is an incredibly useful tool that allows developers to see lower level logs at runtime only when desired. A huge caveat to this approach is that the calls themselves to Logger are still evaluated at runtime. When logging strings like this:\n+\n+```elixir\n+Logger.debug(\"the meaning of life: #{inspect compute(42)}\")\n+```\n+\n+Even if you have configured `level: :info`, the string is still evaluated. Meaning that `compute(42)` is still evaluated and Logger still goes through the motions to decide to actually log or not. This has the potential to degrade performance in a somewhat deceptive manner. This can be side-stepped altogether by passing a zero arity function rather than a string:\n+\n+```elixir\n+Logger.debug(fn -> \"the meaning of life: #{inspect process(42)}\" end)\n+```\n+\n+That way only the minimal work is done by Logger to check whether the message is at a level that should be logged or not. It is possible to take it even a step further with the `:compile_time_purge_level` option. Logger defaults this to `:debug` (not purging anything). This option is true to its name and strips out logs at compile time, preventing the call itself from even existing at runtime. Again, huge caveat: this purge only applies to the `Logger.{debug,info,warn,error}/2` macros and, notably, not to `Logger.log/3`. These two options, along with passing functions instead of strings, facilitate placing logs inside tight recursive functions, well-trodden code paths, or that have expensive to compute input while incurring negligible or even no cost at all in production.\n+\n+Interestingly, Phoenix uses `Logger.log/3` to print the requests we were looking at before. This means that setting `level: debug, compile_time_purge_level: :info` will still print `Logger.log/3` calls that specify a `:debug` level like our request, but `Logger.debug/2` calls will not print. If this seems clever to you, it is, that isn’t a good thing, and worse: it still abuses log semantics while also making it impossible to temporarily turn on debug logging.\n+\n+There is a better way.\n+\n+# A Machete Will Do\n+\n+If you are determined to see your request parameters in production logs, rather than modifying how you configure Logger, it may be sufficient to pass an additional option to your controllers so that they log at the info level rather than debug. Modify `MachampWeb.controller/0` adding `log: :info` to the options passed to the `use/2` macro:\n+\n+```elixir\n+def controller do\n+  quote do\n+    use Phoenix.Controller, namespace: MachampWeb, log: :info\n+    import Plug.Conn\n+    import MachampWeb.Router.Helpers\n+    import MachampWeb.Gettext\n+  end\n+end\n+```\n+\n+The standard `:debug`, `:info`, `:warn`, `:error`, levels can be passed as values to `:log` along with `false` to not log at all which provides functionality somewhat analogous to `:compile_time_purge` in this particular instance. Now our output when hitting the server looks like this:\n+\n+```elixir\n+19:07:16.083 [info] GET /\n+19:07:16.083 [info] Processing with MachampWeb.PageController.index/2\n+  Parameters: %{\"arms\" => \"four\"}\n+  Pipelines: [:browser]\n+19:07:16.083 [info] Sent 200 in 213µs\n+```\n+\n+Hooray! Keep in mind this impacts all of your controllers.\n+\n+# I Need a Knife, Not a Machete\n+\n+If you only want info level logs from some subset of your controllers you simply need a way to indicate that when calling use in your controller. This can be achieved in a couple ways, lets talk through one. Add an additional clause to the `__using__/1` macro in `MachampWeb` to take more than just which function to call and have it accept a value to be supplied to the `:log` option:\n+\n+```elixir\n+defmacro __using__([:controller = which, log]) do\n+  apply(__MODULE__, which, [log])\n+end\n+```\n+\n+Then modify the `controller/0` function in that module to respect that param and default it it to `:debug` if it is not supplied:\n+\n+```elixir\n+def controller(log \\\\ :debug) do\n+  quote bind_quoted: [log: log] do\n+    use Phoenix.Controller, namespace: MachampWeb, log: log\n+    import Plug.Conn\n+    import MachampWeb.Router.Helpers\n+    import MachampWeb.Gettext\n+  end\n+end\n+```\n+\n+If we hit the server now:\n+\n+```\n+19:42:30.980 [info] GET /\n+19:42:30.980 [info] Sent 200 in 221µs\n+```\n+\n+No log, but no crashes either. This means for controllers where you don’t want to modify this behavior at all, you can leave them as is. Lets update our controller to utilize our changes. Modify `MachampWeb.PageController`'s `use/2` call to look like this:\n+\n+```elixir\n+use MachampWeb, [:controller, :info]\n+```\n+\n+One more time, for the people in the back:\n+\n+```elixir\n+19:44:43.260 [info] GET /\n+19:44:43.260 [info] Processing with MachampWeb.PageController.index/2","path":"content/post/thoughtful-logging-in-elixir-a-phoenix-story.md","position":153,"original_position":153,"commit_id":"d1c0c7819f811ca7c849fbe9caba4353cbc929f8","original_commit_id":"d1c0c7819f811ca7c849fbe9caba4353cbc929f8","user":{"login":"danielspofford","id":868014,"avatar_url":"https://avatars1.githubusercontent.com/u/868014?v=4","gravatar_id":"","url":"https://api.github.com/users/danielspofford","html_url":"https://github.com/danielspofford","followers_url":"https://api.github.com/users/danielspofford/followers","following_url":"https://api.github.com/users/danielspofford/following{/other_user}","gists_url":"https://api.github.com/users/danielspofford/gists{/gist_id}","starred_url":"https://api.github.com/users/danielspofford/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/danielspofford/subscriptions","organizations_url":"https://api.github.com/users/danielspofford/orgs","repos_url":"https://api.github.com/users/danielspofford/repos","events_url":"https://api.github.com/users/danielspofford/events{/privacy}","received_events_url":"https://api.github.com/users/danielspofford/received_events","type":"User","site_admin":false},"body":"Nope, we asked phoenix to log at the info level, and it did 👏 .","created_at":"2018-04-07T19:32:09Z","updated_at":"2018-04-07T19:32:09Z","html_url":"https://github.com/verypossible/engineering-blog/pull/7#discussion_r179925872","pull_request_url":"https://api.github.com/repos/verypossible/engineering-blog/pulls/7","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/verypossible/engineering-blog/pulls/comments/179925872"},"html":{"href":"https://github.com/verypossible/engineering-blog/pull/7#discussion_r179925872"},"pull_request":{"href":"https://api.github.com/repos/verypossible/engineering-blog/pulls/7"}},"in_reply_to_id":179884864},"pull_request":{"url":"https://api.github.com/repos/verypossible/engineering-blog/pulls/7","id":180056975,"html_url":"https://github.com/verypossible/engineering-blog/pull/7","diff_url":"https://github.com/verypossible/engineering-blog/pull/7.diff","patch_url":"https://github.com/verypossible/engineering-blog/pull/7.patch","issue_url":"https://api.github.com/repos/verypossible/engineering-blog/issues/7","number":7,"state":"open","locked":false,"title":"Create thoughtful-logging-in-elixir-a-phoenix-story.md","user":{"login":"danielspofford","id":868014,"avatar_url":"https://avatars1.githubusercontent.com/u/868014?v=4","gravatar_id":"","url":"https://api.github.com/users/danielspofford","html_url":"https://github.com/danielspofford","followers_url":"https://api.github.com/users/danielspofford/followers","following_url":"https://api.github.com/users/danielspofford/following{/other_user}","gists_url":"https://api.github.com/users/danielspofford/gists{/gist_id}","starred_url":"https://api.github.com/users/danielspofford/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/danielspofford/subscriptions","organizations_url":"https://api.github.com/users/danielspofford/orgs","repos_url":"https://api.github.com/users/danielspofford/repos","events_url":"https://api.github.com/users/danielspofford/events{/privacy}","received_events_url":"https://api.github.com/users/danielspofford/received_events","type":"User","site_admin":false},"body":"Hey people, quickly throwing up a PR here to solicit some reviews. Not 100% done with my revisions yet but getting there. Tear it apart plz.","created_at":"2018-04-06T21:20:11Z","updated_at":"2018-04-07T19:32:09Z","closed_at":null,"merged_at":null,"merge_commit_sha":"5f9486309ab98592ee7ee767e896133a69e24b69","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/verypossible/engineering-blog/pulls/7/commits","review_comments_url":"https://api.github.com/repos/verypossible/engineering-blog/pulls/7/comments","review_comment_url":"https://api.github.com/repos/verypossible/engineering-blog/pulls/comments{/number}","comments_url":"https://api.github.com/repos/verypossible/engineering-blog/issues/7/comments","statuses_url":"https://api.github.com/repos/verypossible/engineering-blog/statuses/d1c0c7819f811ca7c849fbe9caba4353cbc929f8","head":{"label":"verypossible:thoughtful-logging-in-elixir-a-phoenix-story","ref":"thoughtful-logging-in-elixir-a-phoenix-story","sha":"d1c0c7819f811ca7c849fbe9caba4353cbc929f8","user":{"login":"verypossible","id":5521793,"avatar_url":"https://avatars3.githubusercontent.com/u/5521793?v=4","gravatar_id":"","url":"https://api.github.com/users/verypossible","html_url":"https://github.com/verypossible","followers_url":"https://api.github.com/users/verypossible/followers","following_url":"https://api.github.com/users/verypossible/following{/other_user}","gists_url":"https://api.github.com/users/verypossible/gists{/gist_id}","starred_url":"https://api.github.com/users/verypossible/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/verypossible/subscriptions","organizations_url":"https://api.github.com/users/verypossible/orgs","repos_url":"https://api.github.com/users/verypossible/repos","events_url":"https://api.github.com/users/verypossible/events{/privacy}","received_events_url":"https://api.github.com/users/verypossible/received_events","type":"Organization","site_admin":false},"repo":{"id":80764850,"name":"engineering-blog","full_name":"verypossible/engineering-blog","owner":{"login":"verypossible","id":5521793,"avatar_url":"https://avatars3.githubusercontent.com/u/5521793?v=4","gravatar_id":"","url":"https://api.github.com/users/verypossible","html_url":"https://github.com/verypossible","followers_url":"https://api.github.com/users/verypossible/followers","following_url":"https://api.github.com/users/verypossible/following{/other_user}","gists_url":"https://api.github.com/users/verypossible/gists{/gist_id}","starred_url":"https://api.github.com/users/verypossible/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/verypossible/subscriptions","organizations_url":"https://api.github.com/users/verypossible/orgs","repos_url":"https://api.github.com/users/verypossible/repos","events_url":"https://api.github.com/users/verypossible/events{/privacy}","received_events_url":"https://api.github.com/users/verypossible/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/verypossible/engineering-blog","description":null,"fork":false,"url":"https://api.github.com/repos/verypossible/engineering-blog","forks_url":"https://api.github.com/repos/verypossible/engineering-blog/forks","keys_url":"https://api.github.com/repos/verypossible/engineering-blog/keys{/key_id}","collaborators_url":"https://api.github.com/repos/verypossible/engineering-blog/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/verypossible/engineering-blog/teams","hooks_url":"https://api.github.com/repos/verypossible/engineering-blog/hooks","issue_events_url":"https://api.github.com/repos/verypossible/engineering-blog/issues/events{/number}","events_url":"https://api.github.com/repos/verypossible/engineering-blog/events","assignees_url":"https://api.github.com/repos/verypossible/engineering-blog/assignees{/user}","branches_url":"https://api.github.com/repos/verypossible/engineering-blog/branches{/branch}","tags_url":"https://api.github.com/repos/verypossible/engineering-blog/tags","blobs_url":"https://api.github.com/repos/verypossible/engineering-blog/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/verypossible/engineering-blog/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/verypossible/engineering-blog/git/refs{/sha}","trees_url":"https://api.github.com/repos/verypossible/engineering-blog/git/trees{/sha}","statuses_url":"https://api.github.com/repos/verypossible/engineering-blog/statuses/{sha}","languages_url":"https://api.github.com/repos/verypossible/engineering-blog/languages","stargazers_url":"https://api.github.com/repos/verypossible/engineering-blog/stargazers","contributors_url":"https://api.github.com/repos/verypossible/engineering-blog/contributors","subscribers_url":"https://api.github.com/repos/verypossible/engineering-blog/subscribers","subscription_url":"https://api.github.com/repos/verypossible/engineering-blog/subscription","commits_url":"https://api.github.com/repos/verypossible/engineering-blog/commits{/sha}","git_commits_url":"https://api.github.com/repos/verypossible/engineering-blog/git/commits{/sha}","comments_url":"https://api.github.com/repos/verypossible/engineering-blog/comments{/number}","issue_comment_url":"https://api.github.com/repos/verypossible/engineering-blog/issues/comments{/number}","contents_url":"https://api.github.com/repos/verypossible/engineering-blog/contents/{+path}","compare_url":"https://api.github.com/repos/verypossible/engineering-blog/compare/{base}...{head}","merges_url":"https://api.github.com/repos/verypossible/engineering-blog/merges","archive_url":"https://api.github.com/repos/verypossible/engineering-blog/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/verypossible/engineering-blog/downloads","issues_url":"https://api.github.com/repos/verypossible/engineering-blog/issues{/number}","pulls_url":"https://api.github.com/repos/verypossible/engineering-blog/pulls{/number}","milestones_url":"https://api.github.com/repos/verypossible/engineering-blog/milestones{/number}","notifications_url":"https://api.github.com/repos/verypossible/engineering-blog/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/verypossible/engineering-blog/labels{/name}","releases_url":"https://api.github.com/repos/verypossible/engineering-blog/releases{/id}","deployments_url":"https://api.github.com/repos/verypossible/engineering-blog/deployments","created_at":"2017-02-02T20:18:15Z","updated_at":"2017-02-02T22:57:57Z","pushed_at":"2018-04-06T21:20:12Z","git_url":"git://github.com/verypossible/engineering-blog.git","ssh_url":"git@github.com:verypossible/engineering-blog.git","clone_url":"https://github.com/verypossible/engineering-blog.git","svn_url":"https://github.com/verypossible/engineering-blog","homepage":null,"size":12238,"stargazers_count":0,"watchers_count":0,"language":"HTML","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":2,"license":null,"forks":0,"open_issues":2,"watchers":0,"default_branch":"master"}},"base":{"label":"verypossible:master","ref":"master","sha":"3acd916852985a0bdb604a82e78c08d3079d5233","user":{"login":"verypossible","id":5521793,"avatar_url":"https://avatars3.githubusercontent.com/u/5521793?v=4","gravatar_id":"","url":"https://api.github.com/users/verypossible","html_url":"https://github.com/verypossible","followers_url":"https://api.github.com/users/verypossible/followers","following_url":"https://api.github.com/users/verypossible/following{/other_user}","gists_url":"https://api.github.com/users/verypossible/gists{/gist_id}","starred_url":"https://api.github.com/users/verypossible/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/verypossible/subscriptions","organizations_url":"https://api.github.com/users/verypossible/orgs","repos_url":"https://api.github.com/users/verypossible/repos","events_url":"https://api.github.com/users/verypossible/events{/privacy}","received_events_url":"https://api.github.com/users/verypossible/received_events","type":"Organization","site_admin":false},"repo":{"id":80764850,"name":"engineering-blog","full_name":"verypossible/engineering-blog","owner":{"login":"verypossible","id":5521793,"avatar_url":"https://avatars3.githubusercontent.com/u/5521793?v=4","gravatar_id":"","url":"https://api.github.com/users/verypossible","html_url":"https://github.com/verypossible","followers_url":"https://api.github.com/users/verypossible/followers","following_url":"https://api.github.com/users/verypossible/following{/other_user}","gists_url":"https://api.github.com/users/verypossible/gists{/gist_id}","starred_url":"https://api.github.com/users/verypossible/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/verypossible/subscriptions","organizations_url":"https://api.github.com/users/verypossible/orgs","repos_url":"https://api.github.com/users/verypossible/repos","events_url":"https://api.github.com/users/verypossible/events{/privacy}","received_events_url":"https://api.github.com/users/verypossible/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/verypossible/engineering-blog","description":null,"fork":false,"url":"https://api.github.com/repos/verypossible/engineering-blog","forks_url":"https://api.github.com/repos/verypossible/engineering-blog/forks","keys_url":"https://api.github.com/repos/verypossible/engineering-blog/keys{/key_id}","collaborators_url":"https://api.github.com/repos/verypossible/engineering-blog/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/verypossible/engineering-blog/teams","hooks_url":"https://api.github.com/repos/verypossible/engineering-blog/hooks","issue_events_url":"https://api.github.com/repos/verypossible/engineering-blog/issues/events{/number}","events_url":"https://api.github.com/repos/verypossible/engineering-blog/events","assignees_url":"https://api.github.com/repos/verypossible/engineering-blog/assignees{/user}","branches_url":"https://api.github.com/repos/verypossible/engineering-blog/branches{/branch}","tags_url":"https://api.github.com/repos/verypossible/engineering-blog/tags","blobs_url":"https://api.github.com/repos/verypossible/engineering-blog/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/verypossible/engineering-blog/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/verypossible/engineering-blog/git/refs{/sha}","trees_url":"https://api.github.com/repos/verypossible/engineering-blog/git/trees{/sha}","statuses_url":"https://api.github.com/repos/verypossible/engineering-blog/statuses/{sha}","languages_url":"https://api.github.com/repos/verypossible/engineering-blog/languages","stargazers_url":"https://api.github.com/repos/verypossible/engineering-blog/stargazers","contributors_url":"https://api.github.com/repos/verypossible/engineering-blog/contributors","subscribers_url":"https://api.github.com/repos/verypossible/engineering-blog/subscribers","subscription_url":"https://api.github.com/repos/verypossible/engineering-blog/subscription","commits_url":"https://api.github.com/repos/verypossible/engineering-blog/commits{/sha}","git_commits_url":"https://api.github.com/repos/verypossible/engineering-blog/git/commits{/sha}","comments_url":"https://api.github.com/repos/verypossible/engineering-blog/comments{/number}","issue_comment_url":"https://api.github.com/repos/verypossible/engineering-blog/issues/comments{/number}","contents_url":"https://api.github.com/repos/verypossible/engineering-blog/contents/{+path}","compare_url":"https://api.github.com/repos/verypossible/engineering-blog/compare/{base}...{head}","merges_url":"https://api.github.com/repos/verypossible/engineering-blog/merges","archive_url":"https://api.github.com/repos/verypossible/engineering-blog/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/verypossible/engineering-blog/downloads","issues_url":"https://api.github.com/repos/verypossible/engineering-blog/issues{/number}","pulls_url":"https://api.github.com/repos/verypossible/engineering-blog/pulls{/number}","milestones_url":"https://api.github.com/repos/verypossible/engineering-blog/milestones{/number}","notifications_url":"https://api.github.com/repos/verypossible/engineering-blog/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/verypossible/engineering-blog/labels{/name}","releases_url":"https://api.github.com/repos/verypossible/engineering-blog/releases{/id}","deployments_url":"https://api.github.com/repos/verypossible/engineering-blog/deployments","created_at":"2017-02-02T20:18:15Z","updated_at":"2017-02-02T22:57:57Z","pushed_at":"2018-04-06T21:20:12Z","git_url":"git://github.com/verypossible/engineering-blog.git","ssh_url":"git@github.com:verypossible/engineering-blog.git","clone_url":"https://github.com/verypossible/engineering-blog.git","svn_url":"https://github.com/verypossible/engineering-blog","homepage":null,"size":12238,"stargazers_count":0,"watchers_count":0,"language":"HTML","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":2,"license":null,"forks":0,"open_issues":2,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/verypossible/engineering-blog/pulls/7"},"html":{"href":"https://github.com/verypossible/engineering-blog/pull/7"},"issue":{"href":"https://api.github.com/repos/verypossible/engineering-blog/issues/7"},"comments":{"href":"https://api.github.com/repos/verypossible/engineering-blog/issues/7/comments"},"review_comments":{"href":"https://api.github.com/repos/verypossible/engineering-blog/pulls/7/comments"},"review_comment":{"href":"https://api.github.com/repos/verypossible/engineering-blog/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/verypossible/engineering-blog/pulls/7/commits"},"statuses":{"href":"https://api.github.com/repos/verypossible/engineering-blog/statuses/d1c0c7819f811ca7c849fbe9caba4353cbc929f8"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"danielspofford"}}