{"id":55494,"date":"2026-08-19T15:00:15","date_gmt":"2026-08-19T22:00:15","guid":{"rendered":"https:\/\/www.griddb.net\/?p=55494"},"modified":"2026-08-19T15:00:15","modified_gmt":"2026-08-19T22:00:15","slug":"one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash","status":"publish","type":"post","link":"https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/","title":{"rendered":"One Device Stream, Two Pipelines: Routing IoT Metrics and Logs into GridDB Cloud with Azure IoT Hub, Telegraf, and Logstash"},"content":{"rendered":"<p>\nIoT devices can be messy and unweildy. The same sensor that reports a clean temperature reading one second will spit out an ugly, text-based firmware error the next, with both arriving in the same stream. If you force a single tool to handle that mixed data input, you end up compromising somewhere: metrics agents are miserable at parsing free-form text, and log processors are wasteful for high-volume numeric data.\n<\/p>\n<p>\nIn this article we will not compromise. We will build a pipeline that splits the stream at the front door and hands each half to the tool that is good at it:\n<\/p>\n<ul>\n<li><strong>Telegraf<\/strong>, InfluxData&#8217;s metrics agent, consumes the clean JSON metrics.<\/li>\n<li><strong>Logstash<\/strong>, Elastic&#8217;s log processor, uses its grok filters to chop the unstructured firmware errors into queryable fields.<\/li>\n<li><strong>Azure IoT Hub<\/strong> sits in front of both, acting as the router that routes each message to the right lane.<\/li>\n<li><strong>GridDB Cloud<\/strong> is the destination for everything, using the official Telegraf and Logstash output plugins from the GridDB Cloud v3.2 bundled third-party plugin pack.<\/li>\n<\/ul>\n<p>\nIf you read our previous article on <a href=\"https:\/\/www.griddb.net\/en\/blog\/storing-opentelemetry-metrics-traces-and-logs-in-griddb-cloud-with-kafka\/\">storing OpenTelemetry signals in GridDB Cloud with Kafka<\/a>, this architecture will feel familiar: a producer at the edge, a routing\/buffering layer in the middle, specialized processors, and GridDB Cloud as the vault at the end. The difference is that this time there is zero custom code. Where the Kafka pipeline needed a hand-written Go bridge to flatten OTLP payloads, every stage here is an off-the-shelf component wired together with configuration.\n<\/p>\n<p>\nThough this blog uses the GridDB Web API, let&#8217;s not forget about this recent release: GridDB Cloud v3.2&#8217;s non-WebAPI connection support. If you have not read about that release yet, start here: <a href=\"https:\/\/www.griddb.net\/en\/blog\/connecting-to-griddb-cloud-v3-2-from-your-local-dev-environment-no-vpn-no-vnet-peering\/\">Connecting to GridDB Cloud v3.2 from Your Local Dev Environment<\/a>.\n<\/p>\n<h2 id=\"the-architecture-four-layers\">The Architecture: Four Layers<\/h2>\n<p>\nThe architecture for this is a bit strange; it&#8217;s been separated into four parts:\n<\/p>\n<p><strong>Layer 1 \u2014 Full data stream of mixed data (edge).<\/strong> Devices emit both signal types into one stream: structured JSON metrics and unstructured text logs, interleaved. Each message carries a small label (<code>msgType<\/code>) declaring which kind it is.<\/p>\n<p><strong>Layer 2 \u2014 The router (Azure IoT Hub).<\/strong> IoT Hub&#8217;s message routing reads the label and splits the stream: metrics go to one Azure Event Hub, logs go to another. After this point the data is perfectly separated into two independent queues.<\/p>\n<p><strong>Layer 3 \u2014 The specialized processors (Telegraf and Logstash).<\/strong> Because the split already happened, neither tool has to do a job it is bad at. Telegraf pulls from the metrics queue and parses JSON. Logstash pulls from the logs queue (text) and runs grok.<\/p>\n<p><strong>Layer 4 \u2014 Datastore (GridDB Cloud).<\/strong> Both tools have an official GridDB output plugin. Telegraf writes its numbers into an optimized <code>TIME_SERIES<\/code> container; Logstash writes its parsed log rows into its own container. One database, two purpose-built containers.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-plaintext\">                          \u250c\u2500(msgType=&#x27;metric&#x27;)\u2500\u25ba Event Hub eh-metrics \u2500\u25ba Telegraf \u2500\u25ba iot_sensor_metrics \u2510\r\ndevice \u2500\u2500\u25ba Azure IoT Hub \u2500\u2524                                                                             \u251c\u2500 GridDB Cloud\r\n                          \u2514\u2500(msgType=&#x27;log&#x27;)\u2500\u2500\u2500\u2500\u25ba Event Hub eh-logs \u2500\u2500\u2500\u2500\u25ba Logstash \u2500\u25ba iot_firmware_logs  \u2518<\/code><\/pre>\n<\/div>\n<p>\nThe two Event Hubs in the middle are queues. The Azure IoT Hub pushes into them; Telegraf and Logstash pull from them at their own pace. That decoupling is what makes the pipeline resilient: if Logstash goes down for an hour, log messages simply pile up in <code>eh-logs<\/code> and get drained when it comes back. Nothing is lost and nothing blocks the devices.\n<\/p>\n<h2 id=\"setting-up-the-azure-side\">Setting Up the Azure Side<\/h2>\n<p>\nWe will keep this section brief since the Azure resources are standard. You need:\n<\/p>\n<ul>\n<li><strong>A resource group<\/strong> to hold everything.<\/li>\n<li><strong>An Event Hubs namespace<\/strong> (Basic tier is fine \u2014 both consumers speak AMQP natively, so you do not need the Kafka-compatible endpoint that Standard tier adds) containing two event hubs: <code>eh-metrics<\/code> and <code>eh-logs<\/code>.<\/li>\n<li><strong>Two authorization rules per event hub<\/strong>: one with <code>Send<\/code> rights (used by IoT Hub&#8217;s routing endpoints) and one with <code>Listen<\/code> rights (used by Telegraf and Logstash). Keeping them separate is necessary because mixing them up produces a very specific error.<\/li>\n<li><strong>An IoT Hub<\/strong> on the <strong>B1 tier or above<\/strong>. The free F1 tier allows only <em>one<\/em> custom routing endpoint, and this architecture needs two. IoT Hub names are also globally unique across Azure, so pick something distinctive.<\/li>\n<li><strong>Two custom endpoints<\/strong> on the IoT Hub, each pointing at one event hub via its <code>Send<\/code> connection string.<\/li>\n<li><strong>Two message routes<\/strong>, which are the heart of the whole design:<\/li>\n<\/ul>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">$ az iot hub message-route create -g &lt;resource-group&gt; -n &lt;iot-hub-name&gt; \\\r\n$   --route-name metrics-route --endpoint-name ep-metrics --source devicemessages \\\r\n$   --condition &quot;msgType = &#x27;metric&#x27;&quot;\r\n\r\n$ az iot hub message-route create -g &lt;resource-group&gt; -n &lt;iot-hub-name&gt; \\\r\n$   --route-name logs-route --endpoint-name ep-logs --source devicemessages \\\r\n$   --condition &quot;msgType = &#x27;log&#x27;&quot;<\/code><\/pre>\n<\/div>\n<ul>\n<li><strong>A device identity<\/strong> for the simulator to authenticate as.<\/li>\n<\/ul>\n<p>\nLet&#8217;s discuss a couple of issues up front: first, IoT Hub routing cannot inspect an arbitrary message body to decide &#8220;is this JSON or plain text&#8221; \u2014 body-based routing queries only work when the message declares <code>contentType = application\/json<\/code> and <code>contentEncoding = utf-8<\/code>, and plain-text bodies are opaque to the routing engine entirely. The robust pattern is what we use here: the device stamps an application property (<code>msgType<\/code>) on every message and the routes filter on that property. Second, once you add custom routes, IoT Hub&#8217;s fallback route to the built-in endpoint is disabled by default \u2014 any message that matches no route is dropped, so a typo in the property name fails silently. Third, on recent Azure CLI versions, creating an event hub on Basic tier requires passing <code>--cleanup-policy Delete --retention-time 24<\/code> together (the CLI&#8217;s defaults request 7-day retention, which Basic rejects, while the retention flag alone trips a serialization error).\n<\/p>\n<h2 id=\"layer-1-the-device-simulator\">Layer 1: The Device Simulator<\/h2>\n<p>\nFor a data source we will use a small Python script that plays the role of an IoT device. It alternates between two data streams: most of the time it sends a clean JSON metrics payload, and roughly 30% of the time it also emits a raw, syslog-style firmware error line. Crucially, it labels every message with the <code>msgType<\/code> application property that the routes key on.\n<\/p>\n<p>\nInstall the SDK with <code>pip install azure-iot-device<\/code>, then:\n<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-python\">import json, random, time\r\nfrom datetime import datetime, timezone\r\nfrom azure.iot.device import IoTHubDeviceClient, Message\r\n\r\nCONN = &quot;&lt;your device connection string&gt;&quot;\r\nclient = IoTHubDeviceClient.create_from_connection_string(CONN)\r\n\r\nERRORS = [&quot;E042 sensor read timeout&quot;, &quot;E107 fw checksum mismatch&quot;, &quot;E019 wifi rssi below threshold&quot;]\r\n\r\nwhile True:\r\n    m = Message(json.dumps({\r\n        &quot;deviceId&quot;: &quot;sensor-001&quot;,\r\n        &quot;temperature&quot;: round(random.uniform(20, 35), 2),\r\n        &quot;humidity&quot;: round(random.uniform(30, 70), 2),\r\n    }))\r\n    m.content_type = &quot;application\/json&quot;\r\n    m.content_encoding = &quot;utf-8&quot;\r\n    m.custom_properties[&quot;msgType&quot;] = &quot;metric&quot;\r\n    client.send_message(m)\r\n\r\n    # occasionally: an ugly text log, labeled for the logs route\r\n    if random.random() &lt; 0.3:\r\n        line = f&#x27;{datetime.now(timezone.utc).strftime(&quot;%Y-%m-%dT%H:%M:%SZ&quot;)} sensor-001 ERROR {random.choice(ERRORS)} ip=192.168.1.{random.randint(2,254)}&#x27;\r\n        l = Message(line)\r\n        l.content_type = &quot;text\/plain&quot;\r\n        l.content_encoding = &quot;utf-8&quot;\r\n        l.custom_properties[&quot;msgType&quot;] = &quot;log&quot;\r\n        client.send_message(l)\r\n\r\n    time.sleep(3)<\/code><\/pre>\n<\/div>\n<p>\nGet the device connection string with:\n<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">$ az iot hub device-identity connection-string show -g &lt;resource-group&gt; -n &lt;iot-hub-name&gt; -d sensor-001 -o tsv<\/code><\/pre>\n<\/div>\n<p>\nBefore wiring up either consumer, it is worth verifying the split with a throwaway script that reads each event hub directly (the <code>azure-eventhub<\/code> pip package makes this a ten-liner). You want to see <em>only<\/em> JSON blobs landing in <code>eh-metrics<\/code> and <em>only<\/em> text lines in <code>eh-logs<\/code>. Debugging the routing and the consumers at the same time is a miserable experience \u2014 confirm the iot hub router is doing its job first.\n<\/p>\n<h2 id=\"layer-3a-telegraf-the-metrics-lane\">Layer 3a: Telegraf \u2014 The Metrics Lane<\/h2>\n<p><a href=\"https:\/\/www.influxdata.com\/time-series-platform\/telegraf\/\">Telegraf<\/a> is InfluxData&#8217;s metrics-collection agent. It offers hundreds of input plugins and dozens of output plugins for shipping collected metrics to a time-series store. The GridDB Telegraf plugin is one of those outputs, and conveniently, Telegraf also ships an <code>eventhub_consumer<\/code> <em>input<\/em> plugin whose primary use case is exactly this: consuming from Azure Event Hubs and IoT Hub.<\/p>\n<h3 id=\"building-telegraf-with-the-griddb-plugin\">Building Telegraf With the GridDB Plugin<\/h3>\n<p>\nThe GridDB output plugin is not distributed as a prebuilt binary, it must be compiled into Telegraf from source. The v3.2 bundled pack ships the plugin source code, which you place into a Telegraf source checkout before building.\n<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">$ mkdir -p ~\/go\/src\/github.com\/influxdata\r\n$ cd ~\/go\/src\/github.com\/influxdata\r\n$ git clone https:\/\/github.com\/influxdata\/telegraf.git\r\n$ cd telegraf\r\n\r\n# Copy in the plugin source from the v3.2 bundle\r\n$ cp -r \/path\/to\/telegraf-output-plugin\/plugins .\/<\/code><\/pre>\n<\/div>\n<p>\nThis places <code>plugins\/outputs\/griddb\/griddb.go<\/code> into the Telegraf source tree. <strong>An important caveat:<\/strong> having the plugin source alone is not sufficient for Telegraf to recognize it. Modern Telegraf (v1.20+) uses a build-tag registration pattern in which each plugin requires a one-line import file under <code>plugins\/outputs\/all\/<\/code>. Without this file, the plugin compiles into the binary as dead code and Telegraf will reject your configuration with <code>undefined but requested output: griddb<\/code>.\n<\/p>\n<p>\nCreate <code>plugins\/outputs\/all\/griddb.go<\/code>:\n<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-go\">\/\/go:build !custom || outputs || outputs.griddb\r\n\r\npackage all\r\n\r\nimport _ &quot;github.com\/influxdata\/telegraf\/plugins\/outputs\/griddb&quot; \/\/ register plugin<\/code><\/pre>\n<\/div>\n<p>\nThen build and verify:\n<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">$ make telegraf\r\n$ .\/telegraf --output-list | grep griddb<\/code><\/pre>\n<\/div>\n<p>\nIf <code>griddb<\/code> appears in the output list, the plugin has been registered correctly.\n<\/p>\n<h3 id=\"configuring-telegraf-as-a-stream-consumer\">Configuring Telegraf as a Stream Consumer<\/h3>\n<p>\nIn our previous walkthrough Telegraf played its traditional role of collecting <em>local<\/em> system metrics. Here it plays a different one: a stream worker that pulls device metrics off an Azure queue. The input section changes; the GridDB output section is identical to before.\n<\/p>\n<p>\nCreate a <code>griddb-iot.conf<\/code>:\n<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">$ [[inputs.eventhub_consumer]]\r\n$     ## The Listen connection string for eh-metrics, including EntityPath\r\n$     connection_string = &quot;Endpoint=sb:\/\/&lt;namespace&gt;.servicebus.windows.net\/;SharedAccessKeyName=consumer-listen;SharedAccessKey=&lt;key&gt;;EntityPath=eh-metrics&quot;\r\n$     data_format = &quot;json&quot;\r\n$     json_string_fields = [&quot;deviceId&quot;]\r\n$     ## Use the time IoT Hub received the message as the row timestamp\r\n$     iot_hub_enqueued_time_as_ts = true\r\n$     ## This becomes the GridDB container name\r\n$     name_override = &quot;iot_sensor_metrics&quot;\r\n\r\n$ [[outputs.griddb]]\r\n$     api_url      = &quot;https:\/\/cloud8737.griddb.com:443\/griddb\/v2\/gs_clustermfcloud8737\/dbs\/nl7QftSt&quot;\r\n$     database     = &quot;${GRIDDB_DATABASE}&quot;\r\n$     cluster_name = &quot;gs_clustermfcloud8737&quot;\r\n$     username     = &quot;${GRIDDB_USERNAME}&quot;\r\n$     password     = &quot;${GRIDDB_PASSWORD}&quot;\r\n$     update_mode  = &quot;append&quot;\r\n$     containers   = []\r\n$     is_timeseries    = true\r\n$     timestamp_column = &quot;timestamp&quot;\r\n\r\n$ [agent]\r\n$     interval            = &quot;10s&quot;\r\n$     flush_interval      = &quot;10s&quot;\r\n$     metric_batch_size   = 1000\r\n$     metric_buffer_limit = 10000\r\n$     omit_hostname       = true\r\n$     debug               = true<\/code><\/pre>\n<\/div>\n<p>\nAdjust the <code>api_url<\/code>, <code>database<\/code>, <code>cluster_name<\/code>, and <code>username<\/code> values to match your own GridDB Cloud instance, and set <code>is_timeseries = true<\/code> so the plugin creates a <code>TIME_SERIES<\/code> container \u2014 the appropriate choice for metrics data.\n<\/p>\n<p>\nTwo details worth calling out. <code>omit_hostname = true<\/code> matters more here than it did before: without it, Telegraf stamps every row with the hostname of the machine <em>running Telegraf<\/em>, which in an IoT pipeline is misleading \u2014 the data came from <code>sensor-001<\/code>, not from your consumer box, and the <code>deviceId<\/code> field already carries the real source. And note that <code>eventhub_consumer<\/code> is a <strong>service input<\/strong>: unlike ordinary polling inputs, it listens for events rather than gathering on an interval, which means the <code>--test<\/code> and <code>--once<\/code> dry-run flags from our previous walkthrough may produce no output for it. Just run it for real:\n<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">$ export GRIDDB_PASSWORD=&#x27;your-password&#x27;\r\n$ .\/telegraf --config griddb-iot.conf<\/code><\/pre>\n<\/div>\n<p>\nWith the simulator running, the debug log shows Telegraf writing batches within seconds, and an <code>iot_sensor_metrics<\/code> container appears in the GridDB Cloud portal with <code>temperature<\/code>, <code>humidity<\/code>, and <code>deviceId<\/code> columns:\n<\/p>\n<p><a href=\"\/wp-content\/uploads\/2026\/08\/image.png\"><img fetchpriority=\"high\" decoding=\"async\" src=\"\/wp-content\/uploads\/2026\/08\/image.png\" alt=\"\" width=\"867\" height=\"598\" class=\"aligncenter size-full wp-image-55498\" srcset=\"\/wp-content\/uploads\/2026\/08\/image.png 867w, \/wp-content\/uploads\/2026\/08\/image-300x207.png 300w, \/wp-content\/uploads\/2026\/08\/image-768x530.png 768w, \/wp-content\/uploads\/2026\/08\/image-600x414.png 600w\" sizes=\"(max-width: 867px) 100vw, 867px\" \/><\/a><\/p>\n<h2 id=\"layer-3b-logstash-the-logs-lane\">Layer 3b: Logstash \u2014 The Logs Lane<\/h2>\n<p>\nWhere Telegraf handles metrics, <a href=\"https:\/\/www.elastic.co\/logstash\">Logstash<\/a> handles logs. It ingests unstructured text events, parses them into structured fields with its grok filter, and ships them to a destination of your choice. Our destination is GridDB Cloud, and our source is the <code>eh-logs<\/code> queue full of raw firmware error lines.\n<\/p>\n<h3 id=\"installing-logstash\">Installing Logstash<\/h3>\n<p>\nThe bundle&#8217;s README points to a yum-based CentOS install path. On macOS we will download the tarball directly from Elastic \u2014 Homebrew&#8217;s <code>elastic\/tap<\/code> formula is currently broken on recent Homebrew versions.\n<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">$ mkdir -p ~\/logstash-demo &amp;&amp; cd ~\/logstash-demo\r\n\r\n$ curl -O https:\/\/artifacts.elastic.co\/downloads\/logstash\/logstash-9.4.1-darwin-aarch64.tar.gz\r\n$ tar -xzf logstash-9.4.1-darwin-aarch64.tar.gz\r\n$ mv logstash-9.4.1 logstash\r\n\r\n$ .\/logstash\/bin\/logstash --version<\/code><\/pre>\n<\/div>\n<h3 id=\"installing-the-griddb-output-plugin\">Installing the GridDB Output Plugin<\/h3>\n<p>\nThe plugin ships as a prebuilt <code>.gem<\/code>, so no Ruby build step is required. Copy it alongside the Logstash install. Note that the leading <code>.\/<\/code> matters \u2014 without it, <code>logstash-plugin<\/code> treats the argument as a remote plugin name and constructs an invalid URL:\n<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">$ cp \/path\/to\/logstash-output-plugin\/logstash-output-griddb-1.0.0.gem ~\/logstash-demo\/\r\n\r\n$ cd ~\/logstash-demo\r\n$ .\/logstash\/bin\/logstash-plugin install .\/logstash-output-griddb-1.0.0.gem\r\n$ .\/logstash\/bin\/logstash-plugin list | grep griddb<\/code><\/pre>\n<\/div>\n<p>\nThe input side needs no installation at all: the <code>azure_event_hubs<\/code> input plugin ships bundled with Logstash.\n<\/p>\n<h3 id=\"the-logstash-config\">The Logstash Config<\/h3>\n<p>\nSave the following as <code>~\/logstash-demo\/iot-logs-to-griddb.conf<\/code>:\n<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-ruby\">input {\r\n  azure_event_hubs {\r\n    event_hub_connections =&gt; [&quot;Endpoint=sb:\/\/&lt;namespace&gt;.servicebus.windows.net\/;SharedAccessKeyName=consumer-listen;SharedAccessKey=&lt;key&gt;;EntityPath=eh-logs&quot;]\r\n    initial_position =&gt; &quot;beginning&quot;\r\n  }\r\n}\r\n\r\nfilter {\r\n  grok {\r\n    match =&gt; {\r\n      &quot;message&quot; =&gt; &#x27;%{TIMESTAMP_ISO8601:log_ts} %{NOTSPACE:device_id} %{LOGLEVEL:level} %{NOTSPACE:error_code} %{DATA:error_msg} ip=%{IP:device_ip}&#x27;\r\n    }\r\n  }\r\n\r\n  date {\r\n    match =&gt; [ &quot;log_ts&quot;, &quot;ISO8601&quot; ]\r\n    target =&gt; &quot;@timestamp&quot;\r\n  }\r\n\r\n  mutate {\r\n    remove_field =&gt; [ &quot;message&quot;, &quot;log_ts&quot;, &quot;event&quot;, &quot;log&quot;, &quot;@version&quot;, &quot;host&quot; ]\r\n  }\r\n}\r\n\r\noutput {\r\n  stdout { codec =&gt; rubydebug }\r\n\r\n  griddb {\r\n    host        =&gt; &quot;https:\/\/cloud8737.griddb.com:443&quot;\r\n    cluster     =&gt; &quot;your-cluster&quot;\r\n    database    =&gt; &quot;your-database&quot;\r\n    container   =&gt; &quot;iot_firmware_logs&quot;\r\n    username    =&gt; &quot;your-username&quot;\r\n    password    =&gt; &quot;${GRIDDB_PASSWORD}&quot;\r\n    insert_mode =&gt; &quot;append&quot;\r\n  }\r\n}<\/code><\/pre>\n<\/div>\n<p>\nThe grok filter does the core work: it takes a raw line like\n<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-plaintext\">2026-07-16T23:28:33Z sensor-001 ERROR E107 fw checksum mismatch ip=192.168.1.99<\/code><\/pre>\n<\/div>\n<p>\nand breaks it into <code>device_id<\/code>, <code>level<\/code>, <code>error_code<\/code>, <code>error_msg<\/code>, and <code>device_ip<\/code> fields. The <code>date<\/code> filter takes the timestamp embedded <em>inside<\/em> the log line \u2014 the moment the device actually recorded the error \u2014 and uses it as the event&#8217;s real timestamp, rather than the moment Logstash happened to process it.\n<\/p>\n<p>\nThe connection string deserves special attention, because it bit us twice while building this. It must be the complete string \u2014 starting with <code>Endpoint=sb:\/\/<\/code> and ending with <code>EntityPath=eh-logs<\/code> \u2014 or the plugin fails at startup with <code>Error parsing event hub string name for connection<\/code>. And it must use the <strong>Listen<\/strong> authorization rule, not the Send rule you gave to IoT Hub&#8217;s routing endpoints. If you paste the Send string, Logstash connects successfully, discovers the partitions, and then fails on every receive with <code>Unauthorized access. 'Listen' claim(s) are required<\/code> \u2014 an error that is easy to miss in the AMQP log spam.\n<\/p>\n<p>\nYou will also see a startup warning that no <code>storage_connection_string<\/code> is configured. Logstash uses an Azure Storage account to checkpoint its position across restarts and to coordinate multiple Logstash instances; for a single-instance demo it is safe to ignore, at the cost of re-reading the queue from the beginning on each restart.\n<\/p>\n<h3 id=\"running-it\">Running It<\/h3>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">$ cd ~\/logstash-demo\r\n$ export GRIDDB_PASSWORD=&#x27;your-password&#x27;\r\n$ .\/logstash\/bin\/logstash -f iot-logs-to-griddb.conf<\/code><\/pre>\n<\/div>\n<p>\nStartup takes 20\u201330 seconds. Because of <code>initial_position => \"beginning\"<\/code>, Logstash immediately drains everything sitting in <code>eh-logs<\/code>, so you should see a burst of parsed events scroll past in the <code>rubydebug<\/code> output, then a steady trickle as the simulator keeps emitting. Each one becomes a row in the <code>iot_firmware_logs<\/code> container:\n<\/p>\n<p><a href=\"\/wp-content\/uploads\/2026\/08\/image-1.png\"><img decoding=\"async\" src=\"\/wp-content\/uploads\/2026\/08\/image-1.png\" alt=\"\" width=\"1569\" height=\"1049\" class=\"aligncenter size-full wp-image-55497\" srcset=\"\/wp-content\/uploads\/2026\/08\/image-1.png 1569w, \/wp-content\/uploads\/2026\/08\/image-1-300x201.png 300w, \/wp-content\/uploads\/2026\/08\/image-1-1024x685.png 1024w, \/wp-content\/uploads\/2026\/08\/image-1-768x513.png 768w, \/wp-content\/uploads\/2026\/08\/image-1-1536x1027.png 1536w, \/wp-content\/uploads\/2026\/08\/image-1-600x401.png 600w\" sizes=\"(max-width: 1569px) 100vw, 1569px\" \/><\/a><\/p>\n<p>\nThe resulting schema is clean: one self-documenting column per parsed field, and timestamps that reflect when each error actually occurred on the device.\n<\/p>\n<h2 id=\"what-lands-in-griddb-cloud\">What Lands in GridDB Cloud<\/h2>\n<p>\nAt this point the full pipeline is live. Two containers, each shaped by the tool that filled it:\n<\/p>\n<ul>\n<li><strong><code>iot_sensor_metrics<\/code><\/strong> \u2014 a <code>TIME_SERIES<\/code> container with one row per metrics message: <code>timestamp<\/code>, <code>deviceId<\/code>, <code>temperature<\/code>, <code>humidity<\/code>. Created and populated by Telegraf.<\/li>\n<li><strong><code>iot_firmware_logs<\/code><\/strong> \u2014 one row per parsed firmware error: <code>timestamp<\/code>, <code>device_id<\/code>, <code>level<\/code>, <code>error_code<\/code>, <code>error_msg<\/code>, <code>device_ip<\/code>. Created and populated by Logstash.<\/li>\n<\/ul>\n<p>\nThe same device produced both, the same IoT Hub received both, and the same database stores both \u2014 but each signal traveled its own lane and was processed by the tool built for it.\n<\/p>\n<p><a href=\"\/wp-content\/uploads\/2026\/08\/architecture-diagram.png\"><img decoding=\"async\" src=\"\/wp-content\/uploads\/2026\/08\/architecture-diagram.png\" alt=\"\" width=\"2560\" height=\"1340\" class=\"aligncenter size-full wp-image-55499\" srcset=\"\/wp-content\/uploads\/2026\/08\/architecture-diagram.png 2560w, \/wp-content\/uploads\/2026\/08\/architecture-diagram-300x157.png 300w, \/wp-content\/uploads\/2026\/08\/architecture-diagram-1024x536.png 1024w, \/wp-content\/uploads\/2026\/08\/architecture-diagram-768x402.png 768w, \/wp-content\/uploads\/2026\/08\/architecture-diagram-1536x804.png 1536w, \/wp-content\/uploads\/2026\/08\/architecture-diagram-2048x1072.png 2048w, \/wp-content\/uploads\/2026\/08\/architecture-diagram-600x314.png 600w\" sizes=\"(max-width: 2560px) 100vw, 2560px\" \/><\/a><\/p>\n<p>\nA note on topology: in this walkthrough, one machine plays every role \u2014 it runs the simulator, Telegraf, and Logstash, so the data flows out to Azure and right back. That looks redundant locally, but it is actually the proof that the architecture is decoupled: no component knows where the others run, because they only ever talk to Azure endpoints. In production, the simulator becomes a fleet of real devices in the field (each with its own IoT Hub device identity), and Telegraf and Logstash become small VMs or containers colocated in the same Azure region as the Event Hubs namespace. Everything in between stays exactly the same.\n<\/p>\n<h2 id=\"extra-grafana-dashboards-over-griddb-cloud\">Extra: Grafana Dashboards Over GridDB Cloud<\/h2>\n<p>\nThe pipeline above is complete on its own, but if you want to visualize what you just ingested, GridDB also ships a Grafana data source plugin in the v3.2 bundle. The installation is somewhat involved, so we are including it here.\n<\/p>\n<p><strong>Important:<\/strong> this plugin is built on AngularJS, which Grafana deprecated in v11 and <strong>fully removed in v12<\/strong>. There is no flag or workaround on modern Grafana, as the framework is no longer present. For now, you need Grafana 10.x. You can read more in Grafana&#8217;s <a href=\"https:\/\/grafana.com\/whats-new\/2025-05-05-removal-of-angular\/\">removal announcement<\/a>.<\/p>\n<h3 id=\"installing-grafana-10\">Installing Grafana 10<\/h3>\n<p>\nDownload the last 10.x release directly from Grafana&#8217;s download page:\n<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">$ mkdir -p ~\/grafana-demo &amp;&amp; cd ~\/grafana-demo\r\n\r\n$ curl -O https:\/\/dl.grafana.com\/oss\/release\/grafana-10.4.15.darwin-arm64.tar.gz\r\n$ tar -xzf grafana-10.4.15.darwin-arm64.tar.gz\r\n$ mv grafana-v10.4.15 grafana<\/code><\/pre>\n<\/div>\n<h3 id=\"enabling-the-plugin\">Enabling the Plugin<\/h3>\n<p>\nTwo configuration changes are needed before the plugin will load. Create <code>~\/grafana-demo\/grafana\/conf\/custom.ini<\/code> (Grafana automatically merges this with the defaults):\n<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-ini\">[plugins]\r\nallow_loading_unsigned_plugins = griddb-datasource\r\n\r\n[security]\r\nangular_support_enabled = true<\/code><\/pre>\n<\/div>\n<p>\nThe first setting whitelists the unsigned plugin; the second enables the AngularJS compatibility mode that Grafana 10 still provides.\n<\/p>\n<p>\nCopy the plugin into Grafana&#8217;s plugins directory:\n<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">$ mkdir -p ~\/grafana-demo\/grafana\/data\/plugins\/griddb-datasource\r\n$ cp -r \/path\/to\/grafana-input-plugin\/dist\/* \\\r\n$       ~\/grafana-demo\/grafana\/data\/plugins\/griddb-datasource\/<\/code><\/pre>\n<\/div>\n<p>\nStart Grafana:\n<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">$ cd ~\/grafana-demo\/grafana\r\n$ .\/bin\/grafana server<\/code><\/pre>\n<\/div>\n<p>\nOpen <code>http:\/\/localhost:3000<\/code>, log in (<code>admin<\/code>\/<code>admin<\/code> by default; you will be prompted to set a new password), and add a new GridDB data source under <strong>Connections \u2192 Data sources<\/strong>. Fill in:\n<\/p>\n<ul>\n<li><strong>Host:<\/strong> <code>https:\/\/cloud8737.griddb.com:443<\/code> (no trailing path \u2014 the plugin appends <code>\/griddb\/v2\/...<\/code> itself)<\/li>\n<li><strong>Cluster:<\/strong> your cluster name<\/li>\n<li><strong>Database:<\/strong> your database name<\/li>\n<li><strong>User \/ Password:<\/strong> your GridDB Cloud credentials<\/li>\n<\/ul>\n<h3 id=\"a-note-on-the-password-field\">A Note on the Password Field<\/h3>\n<p>\nOnce the form is complete, click <strong>Save &#038; Test<\/strong>. If the test fails with a <code>TXN_AUTH_FAILED<\/code> error even though your credentials are correct, the data source form may not have persisted the password to Grafana&#8217;s secure storage (this occurred consistently in our testing). You can confirm by inspecting Grafana&#8217;s sqlite store:\n<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">$ sqlite3 ~\/grafana-demo\/grafana\/data\/grafana.db \\\r\n$   &quot;SELECT name, basic_auth_user, length(secure_json_data) FROM data_source;&quot;<\/code><\/pre>\n<\/div>\n<p>\nIf <code>length(secure_json_data)<\/code> is <code>2<\/code>, the password did not save (<code>{}<\/code> is two characters). The workaround is to set the password via Grafana&#8217;s HTTP API, which writes the credentials properly:\n<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">$ curl -X PUT \\\r\n$   -u &#x27;admin:YOUR_GRAFANA_ADMIN_PASSWORD&#x27; \\\r\n$   -H &quot;Content-Type: application\/json&quot; \\\r\n$   http:\/\/localhost:3000\/api\/datasources\/1 \\\r\n$   -d &#x27;{\r\n$     &quot;id&quot;: 1,\r\n$     &quot;name&quot;: &quot;griddb-datasource&quot;,\r\n$     &quot;type&quot;: &quot;griddb-datasource&quot;,\r\n$     &quot;url&quot;: &quot;https:\/\/cloud8737.griddb.com:443&quot;,\r\n$     &quot;access&quot;: &quot;proxy&quot;,\r\n$     &quot;basicAuth&quot;: true,\r\n$     &quot;basicAuthUser&quot;: &quot;your-griddb-username&quot;,\r\n$     &quot;secureJsonData&quot;: { &quot;basicAuthPassword&quot;: &quot;your-griddb-password&quot; },\r\n$     &quot;jsonData&quot;: {\r\n$       &quot;xgridcluster&quot;: &quot;your-cluster-name&quot;,\r\n$       &quot;xgriddatabase&quot;: &quot;your-database-name&quot;,\r\n$       &quot;minInterval&quot;: &quot;1s&quot;\r\n$     }\r\n$   }&#x27;<\/code><\/pre>\n<\/div>\n<p>\n(Single-quote the <code>-u<\/code> argument if your Grafana admin password contains shell metacharacters.)\n<\/p>\n<p>\nAfter that, <code>secure_json_data<\/code> will contain a long encrypted blob, and the data source will authenticate cleanly.\n<\/p>\n<h3 id=\"querying-your-data\">Querying Your Data<\/h3>\n<p>\nWith the data source connected, build a panel using the plugin&#8217;s query syntax against the containers this pipeline created:\n<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-sh\">$griddb_query_data(iot_sensor_metrics, temperature, select * order by timestamp)<\/code><\/pre>\n<\/div>\n<p>\nThe three arguments are the container, the columns to select, and a TQL clause. You can also use <code>$griddb_container_list<\/code> to populate template variables for a container picker, or <code>$griddb_column_list({container})<\/code> to drive column dropdowns.\n<\/p>\n<h2 id=\"wrapping-up\">Wrapping Up<\/h2>\n<p>\nFour layers, each doing one job:\n<\/p>\n<ul>\n<li><strong>The edge<\/strong> produces a mixed stream of metrics and logs, each message labeled with what it is.<\/li>\n<li><strong>Azure IoT Hub<\/strong> routes on that label, splitting the firehose into two clean queues.<\/li>\n<li><strong>Telegraf and Logstash<\/strong> each consume the lane they are built for \u2014 JSON parsing on one side, grok on the other.<\/li>\n<li><strong>GridDB Cloud<\/strong> stores both signals in purpose-built containers, side by side and queryable together.<\/li>\n<\/ul>\n<p>\nCompared to our Kafka-based OpenTelemetry pipeline, the striking thing about this one is what is missing: there is no bridge, no custom flattening code, no bespoke glue. IoT Hub&#8217;s routing rules replace the splitting logic, and the official <code>eventhub_consumer<\/code> and <code>azure_event_hubs<\/code> input plugins replace the consuming logic. The GridDB Cloud v3.2 bundled plugin pack supplies both output plugins \u2014 one download, and every stage of a cross-cloud IoT pipeline is configuration rather than code.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>IoT devices can be messy and unweildy. The same sensor that reports a clean temperature reading one second will spit out an ugly, text-based firmware error the next, with both arriving in the same stream. If you force a single tool to handle that mixed data input, you end up compromising somewhere: metrics agents are [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":55499,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[121],"tags":[],"class_list":["post-55494","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.1.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>One Device Stream, Two Pipelines: Routing IoT Metrics and Logs into GridDB Cloud with Azure IoT Hub, Telegraf, and Logstash | GridDB: Open Source Time Series Database for IoT<\/title>\n<meta name=\"description\" content=\"IoT devices can be messy and unweildy. The same sensor that reports a clean temperature reading one second will spit out an ugly, text-based firmware\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"One Device Stream, Two Pipelines: Routing IoT Metrics and Logs into GridDB Cloud with Azure IoT Hub, Telegraf, and Logstash | GridDB: Open Source Time Series Database for IoT\" \/>\n<meta property=\"og:description\" content=\"IoT devices can be messy and unweildy. The same sensor that reports a clean temperature reading one second will spit out an ugly, text-based firmware\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/\" \/>\n<meta property=\"og:site_name\" content=\"GridDB: Open Source Time Series Database for IoT\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/griddbcommunity\/\" \/>\n<meta property=\"article:published_time\" content=\"2026-08-19T22:00:15+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.griddb.net\/wp-content\/uploads\/2026\/08\/architecture-diagram.png\" \/>\n\t<meta property=\"og:image:width\" content=\"2560\" \/>\n\t<meta property=\"og:image:height\" content=\"1340\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Israel\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@GridDBCommunity\" \/>\n<meta name=\"twitter:site\" content=\"@GridDBCommunity\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Israel\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"14 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/\"},\"author\":{\"name\":\"Israel\",\"@id\":\"https:\/\/www.griddb.net\/en\/#\/schema\/person\/c8a430e7156a9e10af73b1fbb46c2740\"},\"headline\":\"One Device Stream, Two Pipelines: Routing IoT Metrics and Logs into GridDB Cloud with Azure IoT Hub, Telegraf, and Logstash\",\"datePublished\":\"2026-08-19T22:00:15+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/\"},\"wordCount\":2491,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.griddb.net\/en\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/#primaryimage\"},\"thumbnailUrl\":\"\/wp-content\/uploads\/2026\/08\/architecture-diagram.png\",\"articleSection\":[\"Blog\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/\",\"url\":\"https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/\",\"name\":\"One Device Stream, Two Pipelines: Routing IoT Metrics and Logs into GridDB Cloud with Azure IoT Hub, Telegraf, and Logstash | GridDB: Open Source Time Series Database for IoT\",\"isPartOf\":{\"@id\":\"https:\/\/www.griddb.net\/en\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/#primaryimage\"},\"thumbnailUrl\":\"\/wp-content\/uploads\/2026\/08\/architecture-diagram.png\",\"datePublished\":\"2026-08-19T22:00:15+00:00\",\"description\":\"IoT devices can be messy and unweildy. The same sensor that reports a clean temperature reading one second will spit out an ugly, text-based firmware\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/#primaryimage\",\"url\":\"\/wp-content\/uploads\/2026\/08\/architecture-diagram.png\",\"contentUrl\":\"\/wp-content\/uploads\/2026\/08\/architecture-diagram.png\",\"width\":2560,\"height\":1340},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.griddb.net\/en\/#website\",\"url\":\"https:\/\/www.griddb.net\/en\/\",\"name\":\"GridDB: Open Source Time Series Database for IoT\",\"description\":\"GridDB is an open source time-series database with the performance of NoSQL and convenience of SQL\",\"publisher\":{\"@id\":\"https:\/\/www.griddb.net\/en\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.griddb.net\/en\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.griddb.net\/en\/#organization\",\"name\":\"Fixstars\",\"url\":\"https:\/\/www.griddb.net\/en\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.griddb.net\/en\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png\",\"contentUrl\":\"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png\",\"width\":200,\"height\":83,\"caption\":\"Fixstars\"},\"image\":{\"@id\":\"https:\/\/www.griddb.net\/en\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/griddbcommunity\/\",\"https:\/\/x.com\/GridDBCommunity\",\"https:\/\/www.linkedin.com\/company\/griddb-by-toshiba\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.griddb.net\/en\/#\/schema\/person\/c8a430e7156a9e10af73b1fbb46c2740\",\"name\":\"Israel\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.griddb.net\/en\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/4df8cfc155402a2928d11f80b0220037b8bd26c4f1b19c4598d826e0306e6307?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/4df8cfc155402a2928d11f80b0220037b8bd26c4f1b19c4598d826e0306e6307?s=96&d=mm&r=g\",\"caption\":\"Israel\"},\"url\":\"https:\/\/www.griddb.net\/en\/author\/israel\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"One Device Stream, Two Pipelines: Routing IoT Metrics and Logs into GridDB Cloud with Azure IoT Hub, Telegraf, and Logstash | GridDB: Open Source Time Series Database for IoT","description":"IoT devices can be messy and unweildy. The same sensor that reports a clean temperature reading one second will spit out an ugly, text-based firmware","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:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/","og_locale":"en_US","og_type":"article","og_title":"One Device Stream, Two Pipelines: Routing IoT Metrics and Logs into GridDB Cloud with Azure IoT Hub, Telegraf, and Logstash | GridDB: Open Source Time Series Database for IoT","og_description":"IoT devices can be messy and unweildy. The same sensor that reports a clean temperature reading one second will spit out an ugly, text-based firmware","og_url":"https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/","og_site_name":"GridDB: Open Source Time Series Database for IoT","article_publisher":"https:\/\/www.facebook.com\/griddbcommunity\/","article_published_time":"2026-08-19T22:00:15+00:00","og_image":[{"width":2560,"height":1340,"url":"https:\/\/www.griddb.net\/wp-content\/uploads\/2026\/08\/architecture-diagram.png","type":"image\/png"}],"author":"Israel","twitter_card":"summary_large_image","twitter_creator":"@GridDBCommunity","twitter_site":"@GridDBCommunity","twitter_misc":{"Written by":"Israel","Est. reading time":"14 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/#article","isPartOf":{"@id":"https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/"},"author":{"name":"Israel","@id":"https:\/\/www.griddb.net\/en\/#\/schema\/person\/c8a430e7156a9e10af73b1fbb46c2740"},"headline":"One Device Stream, Two Pipelines: Routing IoT Metrics and Logs into GridDB Cloud with Azure IoT Hub, Telegraf, and Logstash","datePublished":"2026-08-19T22:00:15+00:00","mainEntityOfPage":{"@id":"https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/"},"wordCount":2491,"commentCount":0,"publisher":{"@id":"https:\/\/www.griddb.net\/en\/#organization"},"image":{"@id":"https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/#primaryimage"},"thumbnailUrl":"\/wp-content\/uploads\/2026\/08\/architecture-diagram.png","articleSection":["Blog"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/","url":"https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/","name":"One Device Stream, Two Pipelines: Routing IoT Metrics and Logs into GridDB Cloud with Azure IoT Hub, Telegraf, and Logstash | GridDB: Open Source Time Series Database for IoT","isPartOf":{"@id":"https:\/\/www.griddb.net\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/#primaryimage"},"image":{"@id":"https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/#primaryimage"},"thumbnailUrl":"\/wp-content\/uploads\/2026\/08\/architecture-diagram.png","datePublished":"2026-08-19T22:00:15+00:00","description":"IoT devices can be messy and unweildy. The same sensor that reports a clean temperature reading one second will spit out an ugly, text-based firmware","inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.griddb.net\/en\/blog\/one-device-stream-two-pipelines-routing-iot-metrics-and-logs-into-griddb-cloud-with-azure-iot-hub-telegraf-and-logstash\/#primaryimage","url":"\/wp-content\/uploads\/2026\/08\/architecture-diagram.png","contentUrl":"\/wp-content\/uploads\/2026\/08\/architecture-diagram.png","width":2560,"height":1340},{"@type":"WebSite","@id":"https:\/\/www.griddb.net\/en\/#website","url":"https:\/\/www.griddb.net\/en\/","name":"GridDB: Open Source Time Series Database for IoT","description":"GridDB is an open source time-series database with the performance of NoSQL and convenience of SQL","publisher":{"@id":"https:\/\/www.griddb.net\/en\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.griddb.net\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.griddb.net\/en\/#organization","name":"Fixstars","url":"https:\/\/www.griddb.net\/en\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.griddb.net\/en\/#\/schema\/logo\/image\/","url":"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png","contentUrl":"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png","width":200,"height":83,"caption":"Fixstars"},"image":{"@id":"https:\/\/www.griddb.net\/en\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/griddbcommunity\/","https:\/\/x.com\/GridDBCommunity","https:\/\/www.linkedin.com\/company\/griddb-by-toshiba"]},{"@type":"Person","@id":"https:\/\/www.griddb.net\/en\/#\/schema\/person\/c8a430e7156a9e10af73b1fbb46c2740","name":"Israel","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.griddb.net\/en\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/4df8cfc155402a2928d11f80b0220037b8bd26c4f1b19c4598d826e0306e6307?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/4df8cfc155402a2928d11f80b0220037b8bd26c4f1b19c4598d826e0306e6307?s=96&d=mm&r=g","caption":"Israel"},"url":"https:\/\/www.griddb.net\/en\/author\/israel\/"}]}},"_links":{"self":[{"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/posts\/55494","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/comments?post=55494"}],"version-history":[{"count":4,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/posts\/55494\/revisions"}],"predecessor-version":[{"id":55502,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/posts\/55494\/revisions\/55502"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/media\/55499"}],"wp:attachment":[{"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/media?parent=55494"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/categories?post=55494"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.griddb.net\/en\/wp-json\/wp\/v2\/tags?post=55494"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}