{"id":2988,"date":"2025-01-07T19:49:51","date_gmt":"2025-01-08T01:49:51","guid":{"rendered":"https:\/\/readme.com\/resources\/?p=2988"},"modified":"2025-12-15T17:06:33","modified_gmt":"2025-12-15T23:06:33","slug":"making-better-use-of-herokus-logging-firehose","status":"publish","type":"post","link":"https:\/\/readme.com\/resources\/making-better-use-of-herokus-logging-firehose","title":{"rendered":"Making Better Use of Heroku\u2019s Logging Firehose"},"content":{"rendered":"\n<p>This past month at ReadMe has been quite productive! We&nbsp;<a href=\"https:\/\/wapi.fm\/?ref=blog.readme.com\">hosted a 24 hour API radio station<\/a>,&nbsp;<a href=\"https:\/\/docs.readme.com\/guides\/changelog\/api-explorer-v6-allof-support-better-errors-and-more?ref=blog.readme.com\">deployed a huge v6 of our API explorer<\/a>,&nbsp;<a href=\"https:\/\/blog.readme.com\/readme-flavored-markdown\/\">reimagined our Markdown engine<\/a>, and&nbsp;<a href=\"https:\/\/blog.readme.com\/hoot\/\">released an app on Glitch<\/a>&nbsp;showcasing how we\u2019re able to demo&nbsp;<a href=\"https:\/\/readme.com\/metrics?ref=blog.readme.com\">Developer Metrics<\/a>.<\/p>\n\n\n\n<p>What better time is there than now to tackle a longstanding project that completely rearchitects how we store, and use, HTTP logs?<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"70-psi\">70 psi<\/h3>\n\n\n\n<p>Historically at ReadMe, our usage of HTTP logs has been extremely lackluster. We either didn\u2019t store valuable information, stored too much unnecessary data that made finding issues difficult, if not impossible, or the logging platform we used made navigating logs a chore.<\/p>\n\n\n\n<p>ReadMe runs on Heroku, so the logs that we have access to via&nbsp;<code>heroku logs --tail<\/code>&nbsp;are a raw firehose of data from their&nbsp;<a href=\"https:\/\/devcenter.heroku.com\/articles\/logplex?ref=blog.readme.com\">Logplex router<\/a>. Have you ever turned a hose on a dog and watched them try, and fail, in spectacular fashion, to gobble up all of the water? That was us trying to make use of this supersonic stream of logs.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"elk\">ELK<\/h3>\n\n\n\n<p>I fell in love with&nbsp;<a href=\"https:\/\/www.elastic.co\/kibana?ref=blog.readme.com\">Kibana<\/a>&nbsp;when I was working at Vimeo on its API. The dashboards we were able to construct allowed us to quickly, and easily, find and discover problems in the platform before they became problems. This is something I\u2019ve been evangelizing since coming to ReadMe.<\/p>\n\n\n\n<p>The problem, however, is that there isn\u2019t a one-click Heroku marketplace solution for extracting logs into an Elasticsearch and Kibana stack, like there is for the other logging platforms. Elastic and Kibana are available there, but only as part of a general purpose cloud storage solution. Maybe we can use that somehow\u2026<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"improving-our-http-logs\">Improving our HTTP logs<\/h3>\n\n\n\n<p>Before we dig into how we can roll up something to use Elastic and Kibana, we need to first improve our general purpose HTTP logs. Since we\u2019re a Node shop, we are currently using the&nbsp;<a href=\"https:\/\/www.npmjs.com\/package\/morgan?ref=blog.readme.com\">Morgan<\/a>&nbsp;package for compiling, with the default&nbsp;<code>dev<\/code>&nbsp;format, and outputting our logs to stdout.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/blog.readme.com\/content\/images\/2020\/04\/npm-start---morgan-dev.png\" alt=\"\"\/><\/figure>\n\n\n\n<p>While these logs are great for local development, logging this format in production is less so. There\u2019s no dates, user-identifiable data, or information on the ReadMe project that was used, and we don\u2019t have Heroku\u2019s&nbsp;<a href=\"https:\/\/devcenter.heroku.com\/articles\/http-request-id?ref=blog.readme.com\">X-Request-ID header<\/a>&nbsp;anywhere. We can do better than this, and thankfully Morgan is extensible for us to log whatever we want by constructing some custom middleware and passing it a custom function:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"JavaScript\" class=\"language-JavaScript\">module.exports = inlet =&gt; {\n  return morgan((tokens, req, res) =&gt; {\n    const contentLength = tokens.res(req, res, 'content-length');\n    const responseTime = tokens['response-time'](req, res);\n    const referrer = tokens.referrer(req);\n\n    const metadata = {\n      inlet,\n      isEnterprise: req.project &amp;&amp; req.project.isEnterprise(),\n      project: req.project ? req.project.name : undefined,\n      'x-request-id': tokens.req(req, res, 'x-request-id'),\n    };\n\n    return [\n      tokens['remote-addr'](req, res),\n      '-',\n      `[${tokens.date(req, res, 'clf')}]`,\n      `\"${tokens.method(req)} ${tokens.url(req)} HTTP\/${tokens['http-version'](req)}\"`,\n      tokens.status(req, res),\n      contentLength !== undefined ? contentLength : '-',\n      responseTime ? `${responseTime} ms` : '-',\n      `\"${referrer !== undefined ? referrer : '-'}\"`,\n      `\"${tokens['user-agent'](req)}\"`,\n      '-',\n      JSON.stringify(metadata),\n    ].join(' ');\n  });\n};<\/code><\/pre>\n\n\n\n<p>With a custom logger like this, we\u2019re now able to log a healthy amount of useful information.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/blog.readme.com\/content\/images\/2020\/04\/better-logs.png\" alt=\"\"\/><\/figure>\n\n\n\n<p>But how can we make better use of these logs?<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"logstash\">Logstash<\/h3>\n\n\n\n<p><a href=\"https:\/\/www.elastic.co\/logstash?ref=blog.readme.com\">Logstash<\/a>&nbsp;is a server pipeline that can ingest logs from any number of sources, mutate them, and then send them along to another source (like an Elasticsearch cluster). The problem that we\u2019ve got is that, since we\u2019re on Heroku, we\u2019re still bound by its&nbsp;<a href=\"https:\/\/devcenter.heroku.com\/articles\/logplex?ref=blog.readme.com\">Logplex<\/a>&nbsp;routing layer. Thankfully, Heroku has a service called&nbsp;<a href=\"https:\/\/devcenter.heroku.com\/articles\/log-drains?ref=blog.readme.com\">Log Drains<\/a>&nbsp;that allows you to configure a \u201cdrain\u201d on your logging firehose. This is our golden ticket.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"JavaScript\" class=\"language-JavaScript\">127.0.0.1 - [11\/Apr\/2020:00:35:14 +0000] \"GET \/ HTTP\/1.1\" 200 - 121.675 ms \"-\" \"Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/80.0.3987.163 Safari\/537.36\" - {\"inlet\":\"dash\"}<\/code><\/pre>\n\n\n\n<p>With our newly reformatted logs, we now need to figure out how we can ingest them, and only them, into Elasticsearch. This way, we aren\u2019t just replicating the same old firehose in a new datastore. There is a debugging tool,\u00a0Grok Debugger, that is wonderful at not only teaching you how the Logstash\u00a0<a href=\"https:\/\/www.elastic.co\/guide\/en\/logstash\/current\/plugins-filters-grok.html?ref=blog.readme.com\">Grok filter plugin<\/a>\u00a0works, but assists in constructing a Grok-compatible expression. A similar tool also exists within Kibana, and can be found under \u201cDev Tools.\u201d<\/p>\n\n\n\n<p>With the Grok Debugger, we\u2019re able to construct the following expression to match our new log format:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"JavaScript\" class=\"language-JavaScript\">%{IP:ip_address} - \\[%{DATA:date}\\] \"%{WORD:method} %{URIPATH:url} HTTP\/%{NUMBER:http_version}\" %{NUMBER:http_code} %{DATA:content_length} %{DATA:response_time} ms \"%{DATA:referrer}\" \"%{DATA:user_agent}\" - %{GREEDYDATA:metadata}<\/code><\/pre>\n\n\n\n<p>Some things to note here: Since we log a&nbsp;<code>-<\/code>&nbsp;for&nbsp;<code>content_length<\/code>&nbsp;and&nbsp;<code>response_time<\/code>&nbsp;if we don\u2019t have it, we need to use Grok\u2019s&nbsp;<code>DATA<\/code>&nbsp;pattern instead of&nbsp;<code>NUMBER<\/code>. If we used the latter, the full log wouldn\u2019t get matched because &#8211; isn\u2019t a number. We could potentially clean this up by writing a custom pattern, but c&#8217;est la vie.<\/p>\n\n\n\n<p>From here, we can finally start to build out our Logtash pipeline:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"JavaScript\" class=\"language-JavaScript\">input {\n  tcp {\n    port =&gt; \"1514\"\n  }\n}\n\nfilter {\n  grok {\n    match =&gt; {\n      \"message\" =&gt; '%{IP:ip_address} - \\[%{DATA:date}\\] \"%{WORD:method} %{URIPATH:url} HTTP\/%{NUMBER:http_version}\" %{NUMBER:http_code} %{DATA:content_length} %{DATA:response_time} ms \"%{DATA:referrer}\" \"%{DATA:user_agent}\" - %{GREEDYDATA:metadata}'\n    }\n    add_tag =&gt; [\"readme_web\"]\n    remove_field =&gt; [\"date\", \"host\", \"port\", \"message\"]\n  }\n\n  if \"readme_web\" in [tags] {\n    json {\n      source =&gt; \"metadata\"\n    }\n\n    mutate {\n      convert =&gt; {\n        \"http_code\" =&gt; \"integer\"\n        \"http_version\" =&gt; \"float\"\n        \"isEnterprise\" =&gt; \"boolean\"\n      }\n\n      remove_field =&gt; [\"metadata\"]\n    }\n\n    if [content_length] !~ \"-\" {\n      mutate {\n        convert =&gt; {\n          \"content_length\" =&gt; \"integer\"\n        }\n      }\n    } else {\n      drop {\n        remove_field =&gt; [\"content_length\"]\n      }\n    }\n\n    if [response_time] !~ \"-\" {\n      mutate {\n        convert =&gt; {\n          \"response_time\" =&gt; \"float\"\n        }\n      }\n    } else {\n      drop {\n        remove_field =&gt; [\"response_time\"]\n      }\n    }\n  }\n\n  if \"_grokparsefailure\" in [tags] {\n    drop { }\n  }\n}\n\noutput {\n  if \"readme_web\" in [tags] {\n    elasticsearch {\n      hosts =&gt; \"yourElasticSearchCluster\"\n      user =&gt; \"username\"\n      password =&gt; \"password\"\n      index =&gt; \"readme\"\n    }\n  }\n}\n<\/code><\/pre>\n\n\n\n<p>Some things to note with this pipeline:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>We\u2019re junking\u00a0<code>date<\/code>\u00a0because data gets timestamped anyway in Elastic (as\u00a0<code>@timestamp<\/code>) and it\u2019s silly to manage two dates.<\/li>\n\n\n\n<li>The Logstash\u00a0<a href=\"https:\/\/www.elastic.co\/guide\/en\/logstash\/current\/plugins-filters-mutate.html?ref=blog.readme.com\">Mutate filter plugin<\/a>\u00a0takes our JSON-encoded data in\u00a0<code>metadata<\/code>\u00a0and explodes it out into individual pieces of data.<\/li>\n\n\n\n<li>We\u2019re re-typecasting\u00a0<code>http_code<\/code>,\u00a0<code>http_version<\/code>, and\u00a0<code>isEnterprise<\/code>\u00a0because all logs we\u2019re dealing with are strings.<\/li>\n\n\n\n<li>When Grok is unable to match a log, it adds the\u00a0<code>_grokparsefailure<\/code>\u00a0tag, and since we don\u2019t want junk data in our new logging service, we\u2019re ignoring those. If for some reason you happen to notice that your log totals have dropped, you should debug this tag to see if maybe you\u2019re dumping information, as perhaps your log format changed and your match pattern needs to be updated.<\/li>\n\n\n\n<li>Finally, since we\u2019re shipping logs from Logstash to Elasticsearch, we\u2019re using the\u00a0<a href=\"https:\/\/www.elastic.co\/guide\/en\/logstash\/current\/plugins-filters-elasticsearch.html?ref=blog.readme.com\">Elasticsearch output plugin<\/a>. Logstash offers a handful of other ways to deliver your logs to other services, but for our purposes we want those crispy Kibana dashboards so we\u2019re using ES.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"docker\">Docker<\/h3>\n\n\n\n<p>With our freshly minted Logstash pipeline config, it\u2019s time to see if it actually works. We can use Docker to take care of that. Elastic offers a premade Logstash image that we can easily pull down and use within a Dockerfile:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"JavaScript\" class=\"language-JavaScript\">FROM docker.elastic.co\/logstash\/logstash:7.6.2\nRUN rm -f \/usr\/share\/logstash\/pipeline\/default.conf\nCOPY pipeline\/ \/usr\/share\/logstash\/pipeline\/\nCOPY config\/ \/usr\/share\/logstash\/config\/\n\nEXPOSE 1514\/tcp<\/code><\/pre>\n\n\n\n<p>Drop a&nbsp;<code>logstash.yml<\/code>&nbsp;file in a&nbsp;<code>config\/<\/code>&nbsp;directory with the following:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"JavaScript\" class=\"language-JavaScript\">http.host: \"0.0.0.0\"<\/code><\/pre>\n\n\n\n<p>Save our new pipeline config to&nbsp;<code>pipeline\/default.conf<\/code>, and run the container:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"JavaScript\" class=\"language-JavaScript\">docker build . -t logstash\ndocker run -p 1514:1514 -t logstash\n<\/code><\/pre>\n\n\n\n<p>Now since we\u2019d like to debug this before building out some infrastructure in our production environment, let\u2019s go ham and point our production log drain to our local machine with&nbsp;<a href=\"https:\/\/ngrok.com\/?ref=blog.readme.com\">ngrok<\/a>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"JavaScript\" class=\"language-JavaScript\">ngrok http 1514\n<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/blog.readme.com\/content\/images\/2020\/04\/ngrok.png\" alt=\"\"\/><\/figure>\n\n\n\n<p>With ngrok running, we can now set up a log drain on our Heroku app.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"JavaScript\" class=\"language-JavaScript\">heroku drains:add http:\/\/&lt;&lt;ngrok uuid&gt;&gt;.ngrok.io\n<\/code><\/pre>\n\n\n\n<p>You should immediately start to see connections pouring in to your machine in the ngrok console!<\/p>\n\n\n\n<p>If you\u2019re happy with the results, remove the drain:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"JavaScript\" class=\"language-JavaScript\">heroku drains:remove http:\/\/&lt;&lt;ngrok uuid&gt;&gt;.ngrok.io\n<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>\ud83c\udd98 If you\u2019re seeing \u201cBad Gateway\u201d errors coming through in ngrok, that means either that your Docker container hasn\u2019t finished booting, or Logstash crashed because of an error in your pipeline config. Check your Docker logs for details on what went wrong.<\/p>\n<\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"deployment\">Deployment<\/h3>\n\n\n\n<p>Now that we\u2019ve got a functional Logstash pipeline, and a manageable Docker container, we can deploy this all to production! Spin up a server somewhere, get that HTTP(s) URL, add it as a log drain and sit back and watch all your logs come in.<\/p>\n\n\n\n<p>The reception around the office to these changes, after a scant 12 hours, were pretty enlightening. Not only are we going to be able to have better eyes on the health of our services, but after just a day in production we were able to identify a bug in our frontend JS that was causing users to query an API of ours even when the page wasn\u2019t active. This coupled with a user\u2019s session expiring over the weekend, but their browser remaining open, resulted in this hilarious graph that we wouldn\u2019t have been able to easily identify otherwise:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/blog.readme.com\/content\/images\/2020\/04\/Screen-Shot-2020-04-10-at-6.25.15-PM.png\" alt=\"\"\/><\/figure>\n\n\n\n<p>I\u2019ll end this with a note that I am absolutely not an expert in Logstash, the rest of the ELK stack, or Docker, and learned all of this in less than a week so I am sure that there are things that could be improved with our pipeline or setup.<\/p>\n\n\n\n<p>If you\u2019ve got suggestions for improvements or cleanup, please let us know!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This past month at ReadMe has been quite productive! We&nbsp;hosted a 24 hour API radio station,&nbsp;deployed a huge v6 of our API explorer,&nbsp;reimagined our Markdown engine, and&nbsp;released an app on Glitch&nbsp;showcasing how we\u2019re able to demo&nbsp;Developer Metrics. What better time is there than now to tackle a longstanding project that completely rearchitects how we store, [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"content-type":"","inline_featured_image":false,"footnotes":""},"categories":[22],"tags":[],"ppma_author":[56],"class_list":["post-2988","post","type-post","status-publish","format-standard","hentry","category-api-tips"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v23.0 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Making Better Use of Heroku\u2019s Logging Firehose<\/title>\n<meta name=\"description\" content=\"How ReadMe enhanced Heroku\u2019s logging firehose with structured logs, Logstash, and Elasticsearch for clearer insights.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/readme.com\/resources\/making-better-use-of-herokus-logging-firehose\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Making Better Use of Heroku\u2019s Logging Firehose\" \/>\n<meta property=\"og:description\" content=\"How ReadMe enhanced Heroku\u2019s logging firehose with structured logs, Logstash, and Elasticsearch for clearer insights.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/readme.com\/resources\/making-better-use-of-herokus-logging-firehose\" \/>\n<meta property=\"og:site_name\" content=\"ReadMe: Resource Library\" \/>\n<meta property=\"article:published_time\" content=\"2025-01-08T01:49:51+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-12-15T23:06:33+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/blog.readme.com\/content\/images\/2020\/04\/npm-start---morgan-dev.png\" \/>\n<meta name=\"author\" content=\"Jon Ursenbach\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Miche Nickolaisen\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"6 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/readme.com\/resources\/making-better-use-of-herokus-logging-firehose\",\"url\":\"https:\/\/readme.com\/resources\/making-better-use-of-herokus-logging-firehose\",\"name\":\"Making Better Use of Heroku\u2019s Logging Firehose\",\"isPartOf\":{\"@id\":\"https:\/\/readme.com\/resources\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/readme.com\/resources\/making-better-use-of-herokus-logging-firehose#primaryimage\"},\"image\":{\"@id\":\"https:\/\/readme.com\/resources\/making-better-use-of-herokus-logging-firehose#primaryimage\"},\"thumbnailUrl\":\"https:\/\/blog.readme.com\/content\/images\/2020\/04\/npm-start---morgan-dev.png\",\"datePublished\":\"2025-01-08T01:49:51+00:00\",\"dateModified\":\"2025-12-15T23:06:33+00:00\",\"author\":{\"@id\":\"https:\/\/readme.com\/resources\/#\/schema\/person\/770bcc036178743133b5ba515195981b\"},\"description\":\"How ReadMe enhanced Heroku\u2019s logging firehose with structured logs, Logstash, and Elasticsearch for clearer insights.\",\"breadcrumb\":{\"@id\":\"https:\/\/readme.com\/resources\/making-better-use-of-herokus-logging-firehose#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/readme.com\/resources\/making-better-use-of-herokus-logging-firehose\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/readme.com\/resources\/making-better-use-of-herokus-logging-firehose#primaryimage\",\"url\":\"https:\/\/blog.readme.com\/content\/images\/2020\/04\/npm-start---morgan-dev.png\",\"contentUrl\":\"https:\/\/blog.readme.com\/content\/images\/2020\/04\/npm-start---morgan-dev.png\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/readme.com\/resources\/making-better-use-of-herokus-logging-firehose#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/readme.com\/resources\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Making Better Use of Heroku\u2019s Logging Firehose\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/readme.com\/resources\/#website\",\"url\":\"https:\/\/readme.com\/resources\/\",\"name\":\"ReadMe: Resource Library\",\"description\":\"Making API documentation better for everyone\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/readme.com\/resources\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/readme.com\/resources\/#\/schema\/person\/770bcc036178743133b5ba515195981b\",\"name\":\"Miche Nickolaisen\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/readme.com\/resources\/#\/schema\/person\/image\/a24e32f88df84934c107cef6fa8d3223\",\"url\":\"https:\/\/readme.com\/resources\/wp-content\/uploads\/2024\/06\/IMG_7151-scaled-e1718387764646.jpg\",\"contentUrl\":\"https:\/\/readme.com\/resources\/wp-content\/uploads\/2024\/06\/IMG_7151-scaled-e1718387764646.jpg\",\"caption\":\"Miche Nickolaisen\"},\"description\":\"An Austin resident since 2009, Miche grew up in rural southwestern Missouri. When not working on ReadMe's content marketing, you can find them doing a number of hobbies, including (but not limited to) bouldering, martial arts, playing tabletop RPGs and\/or video games, bullet journaling, and making art. They live with a large menagerie of indoor pets and a smaller (outdoor) menagerie of feral cats they take care of (sometimes including a few possums and raccoons, just for good measure).\",\"url\":\"https:\/\/readme.com\/resources\/author\/miche\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Making Better Use of Heroku\u2019s Logging Firehose","description":"How ReadMe enhanced Heroku\u2019s logging firehose with structured logs, Logstash, and Elasticsearch for clearer insights.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/readme.com\/resources\/making-better-use-of-herokus-logging-firehose","og_locale":"en_US","og_type":"article","og_title":"Making Better Use of Heroku\u2019s Logging Firehose","og_description":"How ReadMe enhanced Heroku\u2019s logging firehose with structured logs, Logstash, and Elasticsearch for clearer insights.","og_url":"https:\/\/readme.com\/resources\/making-better-use-of-herokus-logging-firehose","og_site_name":"ReadMe: Resource Library","article_published_time":"2025-01-08T01:49:51+00:00","article_modified_time":"2025-12-15T23:06:33+00:00","og_image":[{"url":"https:\/\/blog.readme.com\/content\/images\/2020\/04\/npm-start---morgan-dev.png"}],"author":"Jon Ursenbach","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Miche Nickolaisen","Est. reading time":"6 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/readme.com\/resources\/making-better-use-of-herokus-logging-firehose","url":"https:\/\/readme.com\/resources\/making-better-use-of-herokus-logging-firehose","name":"Making Better Use of Heroku\u2019s Logging Firehose","isPartOf":{"@id":"https:\/\/readme.com\/resources\/#website"},"primaryImageOfPage":{"@id":"https:\/\/readme.com\/resources\/making-better-use-of-herokus-logging-firehose#primaryimage"},"image":{"@id":"https:\/\/readme.com\/resources\/making-better-use-of-herokus-logging-firehose#primaryimage"},"thumbnailUrl":"https:\/\/blog.readme.com\/content\/images\/2020\/04\/npm-start---morgan-dev.png","datePublished":"2025-01-08T01:49:51+00:00","dateModified":"2025-12-15T23:06:33+00:00","author":{"@id":"https:\/\/readme.com\/resources\/#\/schema\/person\/770bcc036178743133b5ba515195981b"},"description":"How ReadMe enhanced Heroku\u2019s logging firehose with structured logs, Logstash, and Elasticsearch for clearer insights.","breadcrumb":{"@id":"https:\/\/readme.com\/resources\/making-better-use-of-herokus-logging-firehose#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/readme.com\/resources\/making-better-use-of-herokus-logging-firehose"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/readme.com\/resources\/making-better-use-of-herokus-logging-firehose#primaryimage","url":"https:\/\/blog.readme.com\/content\/images\/2020\/04\/npm-start---morgan-dev.png","contentUrl":"https:\/\/blog.readme.com\/content\/images\/2020\/04\/npm-start---morgan-dev.png"},{"@type":"BreadcrumbList","@id":"https:\/\/readme.com\/resources\/making-better-use-of-herokus-logging-firehose#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/readme.com\/resources\/"},{"@type":"ListItem","position":2,"name":"Making Better Use of Heroku\u2019s Logging Firehose"}]},{"@type":"WebSite","@id":"https:\/\/readme.com\/resources\/#website","url":"https:\/\/readme.com\/resources\/","name":"ReadMe: Resource Library","description":"Making API documentation better for everyone","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/readme.com\/resources\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/readme.com\/resources\/#\/schema\/person\/770bcc036178743133b5ba515195981b","name":"Miche Nickolaisen","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/readme.com\/resources\/#\/schema\/person\/image\/a24e32f88df84934c107cef6fa8d3223","url":"https:\/\/readme.com\/resources\/wp-content\/uploads\/2024\/06\/IMG_7151-scaled-e1718387764646.jpg","contentUrl":"https:\/\/readme.com\/resources\/wp-content\/uploads\/2024\/06\/IMG_7151-scaled-e1718387764646.jpg","caption":"Miche Nickolaisen"},"description":"An Austin resident since 2009, Miche grew up in rural southwestern Missouri. When not working on ReadMe's content marketing, you can find them doing a number of hobbies, including (but not limited to) bouldering, martial arts, playing tabletop RPGs and\/or video games, bullet journaling, and making art. They live with a large menagerie of indoor pets and a smaller (outdoor) menagerie of feral cats they take care of (sometimes including a few possums and raccoons, just for good measure).","url":"https:\/\/readme.com\/resources\/author\/miche"}]}},"authors":[{"term_id":56,"user_id":0,"is_guest":1,"slug":"jon-ursenbach","display_name":"Jon Ursenbach","avatar_url":{"url":"https:\/\/readme.com\/resources\/wp-content\/uploads\/2024\/06\/1579133715034.jpeg","url2x":"https:\/\/readme.com\/resources\/wp-content\/uploads\/2024\/06\/1579133715034.jpeg"},"first_name":"Jon","last_name":"Ursenbach","position":"Lead API Engineer","slogan":"","description":"Jon is our resident expert of all things OpenAPI. He enjoys bad puns, watching genre films, hockey, and had an elderly pug who he still won\u2019t stop talking about."}],"_links":{"self":[{"href":"https:\/\/readme.com\/resources\/wp-json\/wp\/v2\/posts\/2988","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/readme.com\/resources\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/readme.com\/resources\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/readme.com\/resources\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/readme.com\/resources\/wp-json\/wp\/v2\/comments?post=2988"}],"version-history":[{"count":0,"href":"https:\/\/readme.com\/resources\/wp-json\/wp\/v2\/posts\/2988\/revisions"}],"wp:attachment":[{"href":"https:\/\/readme.com\/resources\/wp-json\/wp\/v2\/media?parent=2988"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/readme.com\/resources\/wp-json\/wp\/v2\/categories?post=2988"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/readme.com\/resources\/wp-json\/wp\/v2\/tags?post=2988"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/readme.com\/resources\/wp-json\/wp\/v2\/ppma_author?post=2988"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}