OpenTelemetry for Industrial IoT Observability (2026)
Industrial plants run dozens of edge services simultaneously — MQTT brokers, OPC-UA adapters, protocol gateways, digital twin sync daemons — and when one misbehaves, the standard answer has been “check the logs on that machine.” That approach breaks the moment latency crosses a process boundary or a fault appears three hops away from the device that logged the error. OpenTelemetry industrial IoT observability solves exactly this problem: it gives every edge process a common language for traces, metrics, and logs, and a single collector that forwards all three to any backend you choose.
This post makes a specific argument: the OTel Collector is not just a convenience for edge deployments — it is the architectural keystone that makes observability practical at the plant floor, handling offline buffering, tail sampling, and bandwidth pressure that cloud-first instrumentation cannot address. What this post covers: the OTel data model, Collector internals, a runnable two-tier pipeline, context propagation across MQTT, sampling and cardinality control, trade-offs, and practical recommendations.
Why Industrial IoT Finally Needs OpenTelemetry
The case for OpenTelemetry in plant-floor environments is not about vendor lock-in avoidance — it is about survivability of observability data when connectivity is intermittent. Traditional monitoring agents assume a reliable network path to a central collector; edge deployments cannot. The OTel Collector’s file-based offline buffer and head/tail sampling let a plant continue recording high-fidelity telemetry during a WAN outage and flush it when connectivity resumes.
The OpenTelemetry project reached stable status for traces, metrics, and logs across most major language SDKs as of 2024. The CNCF OpenTelemetry specification defines a vendor-neutral wire format (OTLP) and an API/SDK split that separates instrumentation from export. Industrial use cases are increasingly recognized in OTel SIGs (Special Interest Groups), and vendors like Grafana, Honeycomb, and Elastic all accept OTLP natively.
For broader context on how this fits into a modern edge stack, see the discussion of eBPF-based observability with Pixie and Cilium — that approach complements OTel by providing automatic network-level visibility without code changes, while OTel provides application-level semantic context that eBPF alone cannot carry.
The OTel Signal Model and Why All Three Signals Matter
OpenTelemetry for industrial IoT observability delivers value only when traces, metrics, and logs are treated as a unified system — not three separate pipelines bolted together. Each signal answers a different question, and correlating them is what turns raw telemetry into diagnostic power.

Figure 1: The three OTel signals flow from instrumented edge services through the OTel SDK, into the Collector, and fan out to trace, metric, and log backends.
Traces: End-to-End Request Context
A trace is a directed acyclic graph of spans, each carrying a trace_id, span_id, start/end timestamps, attributes, and status. The critical property is context propagation: the traceparent header (W3C Trace Context specification) allows a trace to cross process and protocol boundaries, so a single logical “sensor reading → digital twin update” transaction can be reconstructed from a chain of spans even when it crosses four different services on three different machines.
For industrial IoT, the right mental model is not “web request” but “control loop transaction.” A single sensor reading may trigger an OPC-UA read, an MQTT publish, a protocol conversion, a REST call to a cloud API, and a database write. Without a trace spanning all six steps, diagnosing a 200 ms latency spike means checking six log files manually.
Metrics: Time-Series Health Signals
OTel metrics support counters, up-down counters, gauges, and histograms. The OTLP metrics data model separates the instrument type (what you’re measuring) from the temporality (cumulative vs. delta) — a distinction that matters when an edge service restarts and cumulative counters reset. For plant-floor use cases, histograms of control-loop latency and gauges of queue depth matter more than request-per-second counts. Define your metrics with explicit units (ms, bytes, {requests}) using the OTel semantic conventions, because backends like Prometheus use units to determine display formatting and alerting thresholds.
Logs: Structured Context Without the Boilerplate
The OTel log data model is a structured record with a SeverityNumber, body, TraceId, SpanId, and resource attributes. The TraceId and SpanId fields are the killer feature: they allow a log line emitted inside a span to be navigated directly from a trace waterfall in Grafana Tempo, collapsing the “find the log for this slow request” workflow from minutes to seconds. Existing logging frameworks (Zap, logrus, Log4j, Python logging) can be bridged to OTel via log appenders — no rewrite required.
OTel Collector Internals: The Edge-Critical Component
The OTel Collector is the load-bearing piece of any production industrial IoT observability deployment. It receives telemetry from SDK exporters, applies processing, and forwards to one or more backends — but at the edge, it does three things no SDK exporter can: offline buffering, tail-based sampling, and fan-out routing.

Figure 2: Inside the OTel Collector — multiple receivers feed processor chains that apply batching, filtering, attribute enrichment, tail sampling, and memory limiting before data reaches exporters.
Receivers: Ingest from Anything
The Collector’s receiver catalog covers OTLP (gRPC and HTTP), Prometheus scrape, Jaeger, Zipkin, syslog, filelog, and a growing list of vendor-specific protocols. For industrial environments the filelog receiver (tailing structured log files from legacy processes) and prometheus receiver (scraping existing Prometheus endpoints on PLCs or gateways) are often more useful than OTLP on day one, because most plant-floor software does not yet ship with OTel SDKs embedded.
An important note on MQTT: MQTT is not yet a first-class receiver in the upstream Collector distribution as of mid-2026. Custom receivers can be built using the Collector Builder (ocb), but for production use, the common pattern is to run a small adapter service that bridges MQTT topics to OTLP — either using a language SDK with a manual span-per-message approach, or a purpose-built connector. This is a genuine gap in the ecosystem and should be a known constraint in your architecture.
Processors: Where the Real Work Happens
The processor chain is the most important tuning surface in the Collector config. Key processors for edge deployments:
batchprocessor — groups spans/metrics/logs into configurable time/size windows before export. This is essential on constrained uplinks; exporting one span at a time produces orders-of-magnitude more overhead than batching hundreds.filterprocessor — drops telemetry by attribute, metric name, or log body. Use it to strip high-volume, low-value signals (heartbeat spans, watchdog metrics) before they consume bandwidth.attributesprocessor — adds, removes, or hashes attributes. This is how you attach asset identity to every span:resource.site.id,resource.asset.type,resource.line.number.tail_samplingprocessor — evaluates complete traces (not individual spans) against a policy set after the fact. This lets you keep 100% of error traces and slow traces while dropping a large fraction of healthy fast traces, rather than making a random head-sampling decision at trace start.memory_limiterprocessor — enforces a hard ceiling on Collector memory use. Place it first in every pipeline. An OOM-killed Collector causes a telemetry gap; the memory limiter gracefully applies backpressure and drops tail signals before that happens.
Exporters: Route to Multiple Backends
Exporters send processed telemetry to backends. The otlp exporter (to a gateway Collector or a vendor endpoint) handles compression and retry with exponential backoff. The prometheusremotewrite exporter pushes metrics to any Prometheus-compatible backend. The loki exporter pushes structured logs. The file exporter writes NDJSON to disk — this is your offline buffer: configure it to write to a persisted volume, and a separate process or a scheduled Collector restart will drain it when connectivity returns.
Building the Two-Tier Pipeline: Edge Collector to Gateway to Backend
A single-tier Collector (edge → cloud) works for small deployments but breaks under WAN bandwidth constraints, intermittent connectivity, and the need to aggregate telemetry from dozens of edge nodes before it reaches the backend. The two-tier topology solves this.

Figure 3: Edge Collectors run one per physical node or Kubernetes DaemonSet pod, apply local tail sampling and buffering, and forward over a compressed TLS OTLP connection to a site-level Gateway Collector that fans out to Prometheus, Tempo, and Loki.
Edge Collector Configuration
Here is a minimal but production-representative otelcol-edge.yaml. This is a working configuration, not pseudocode.
# otelcol-edge.yaml — runs on each edge node / K8s DaemonSet pod
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
http:
endpoint: "0.0.0.0:4318"
filelog:
include: ["/var/log/edge-services/*.log"]
operators:
- type: json_parser
- type: severity_parser
parse_from: attributes.level
processors:
memory_limiter:
check_interval: 1s
limit_mib: 256
spike_limit_mib: 64
attributes/add_resource:
actions:
- key: site.id
value: "plant-floor-a"
action: insert
- key: asset.type
value: "opc-ua-gateway"
action: insert
batch:
send_batch_size: 512
timeout: 5s
tail_sampling:
decision_wait: 10s
num_traces: 50000
policies:
- name: errors-policy
type: status_code
status_code: {status_codes: [ERROR]}
- name: slow-traces-policy
type: latency
latency: {threshold_ms: 500}
- name: probabilistic-policy
type: probabilistic
probabilistic: {sampling_percentage: 5}
filter/drop_heartbeats:
spans:
exclude:
match_type: strict
attributes:
- key: span.name
value: "heartbeat"
exporters:
otlp/gateway:
endpoint: "gateway-collector.plant.internal:4317"
tls:
insecure: false
ca_file: /etc/otel/certs/ca.crt
compression: gzip
retry_on_failure:
enabled: true
initial_interval: 5s
max_interval: 60s
max_elapsed_time: 300s
file/offline_buffer:
path: /var/otel/buffer/traces.ndjson
rotation:
max_megabytes: 500
max_days: 3
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, attributes/add_resource, batch, tail_sampling, filter/drop_heartbeats]
exporters: [otlp/gateway, file/offline_buffer]
logs:
receivers: [otlp, filelog]
processors: [memory_limiter, attributes/add_resource, batch]
exporters: [otlp/gateway]
metrics:
receivers: [otlp]
processors: [memory_limiter, attributes/add_resource, batch]
exporters: [otlp/gateway]
A few things to note in this config. The memory_limiter is first in every processor chain — this is mandatory, not optional. The tail_sampling processor requires all spans of a trace to arrive at the same Collector instance; this works in the edge-tier because each edge node runs its own Collector. Once traces fan in to the gateway, tail sampling at the gateway level is possible but requires a loadbalancing exporter at the edge tier to route all spans of a given trace_id to the same gateway instance. The file/offline_buffer is a safety net, not a primary path; size it to hold several hours of telemetry at your expected data rate.
Instrumenting an Edge Service
For a Go-based edge service (common for OPC-UA adapters and MQTT bridges):
// main.go — minimal OTel SDK setup for an edge service
package main
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
"google.golang.org/grpc"
)
func initTracer(ctx context.Context) (*sdktrace.TracerProvider, error) {
res, err := resource.New(ctx,
resource.WithAttributes(
semconv.ServiceName("opc-ua-gateway"),
semconv.ServiceVersion("1.4.2"),
// Industrial asset identity
attribute.String("site.id", "plant-floor-a"),
attribute.String("asset.line", "line-3"),
),
)
if err != nil {
return nil, err
}
conn, err := grpc.DialContext(ctx, "localhost:4317",
grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return nil, err
}
exporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithGRPCConn(conn))
if err != nil {
return nil, err
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(res),
// Head sampling: keep everything at edge, let tail sampler decide
sdktrace.WithSampler(sdktrace.AlwaysSample()),
)
otel.SetTracerProvider(tp)
return tp, nil
}
The resource.WithAttributes call is where you attach asset identity. Every span exported by this process will carry site.id, asset.line, and standard service.name / service.version attributes — without any per-span instrumentation call. This is how you build dashboards that filter by plant, line, or asset type without post-hoc enrichment.
Context Propagation Across MQTT
MQTT’s publish/subscribe model creates a context propagation challenge: there is no standard header field in MQTT packets equivalent to HTTP’s traceparent. The W3C Trace Context Level 2 draft proposes MQTT user-property injection, but as of mid-2026 this is not universally adopted.

Figure 4: A trace spans a PLC sensor read, MQTT publish with traceparent injected as a user property, gateway service extraction, and export — resulting in a single linked trace visible in Tempo.
The practical approach is to inject the W3C traceparent value as an MQTT 5.0 user property on publish:
# Python MQTT publisher — inject trace context into MQTT 5.0 user properties
import paho.mqtt.client as mqtt
from opentelemetry import trace, propagate
from opentelemetry.propagators.textmap import DefaultSetter
tracer = trace.get_tracer("sensor-publisher")
def publish_reading(client: mqtt.Client, topic: str, payload: bytes):
with tracer.start_as_current_span("mqtt.publish") as span:
span.set_attribute("messaging.system", "mqtt")
span.set_attribute("messaging.destination", topic)
# Inject W3C traceparent into a carrier dict
carrier = {}
propagate.inject(carrier, setter=DefaultSetter())
# carrier = {"traceparent": "00-<trace_id>-<span_id>-01"}
# Convert to MQTT 5 user properties list
user_props = list(carrier.items())
props = mqtt.Properties(mqtt.PacketTypes.PUBLISH)
props.UserProperty = user_props
client.publish(topic, payload, properties=props)
On the subscriber side, extract the carrier dict from user properties before creating the child span:
def on_message(client, userdata, msg):
# Extract trace context from MQTT 5 user properties
carrier = dict(msg.properties.UserProperty) if hasattr(msg.properties, "UserProperty") else {}
ctx = propagate.extract(carrier)
with tracer.start_as_current_span(
"mqtt.receive",
context=ctx,
kind=trace.SpanKind.CONSUMER
) as span:
span.set_attribute("messaging.system", "mqtt")
span.set_attribute("messaging.destination", msg.topic)
process_message(msg.payload)
For MQTT 3.1.1 (which lacks user properties), you have two options: embed the traceparent in a JSON wrapper around the payload, or use a side-channel (a dedicated “correlation” topic) — both are workarounds with trade-offs discussed below.
Resource Attributes: Asset Identity as a First-Class Citizen
One of the most underused features of OTel for industrial deployments is the resource object. While a web service’s resource typically carries service.name and k8s.pod.name, an industrial edge service should carry a richer identity:
# resource attributes for an industrial edge service
resource:
attributes:
service.name: "opc-ua-adapter"
service.version: "2.1.0"
# Industrial semantic conventions (not yet in upstream semconv — treat as custom)
site.id: "plant-floor-a"
site.region: "us-midwest"
asset.type: "opc-ua-gateway"
asset.vendor: "Siemens"
asset.serial_number: "SIM-482-A" # hash or omit if PII concern
line.number: "3"
cell.number: "7"
deployment.environment: "production"
These attributes flow automatically onto every span, metric data point, and log record produced by the service. When you query Grafana Tempo for all slow traces on Line 3 of Plant A, you get that result from a single attribute filter — no log parsing, no manual tagging of individual spans.
The OTel semantic conventions for industrial systems are still evolving. The CNCF OpenTelemetry SIG has ongoing work on manufacturing and IIoT conventions. Until those stabilize, define your own attribute namespace consistently (e.g., site.*, asset.*, line.*) and document it in a shared schema registry so all teams use the same key names.
Sampling and Cardinality Control
Sampling and cardinality are the two cost levers in any OTel deployment. Getting them wrong in either direction is expensive: too little sampling fills storage with redundant healthy-path data; too many high-cardinality metric labels blow up Prometheus storage and query time.

Figure 5: Every span or metric point traverses head-sampling, tail-sampling policy matching, a cardinality check, and a memory-limiter gate before reaching the exporter.
Head vs. Tail Sampling
Head sampling (a probabilistic decision at trace start) is simple and low-overhead but throws away error traces and slow traces at the same rate as healthy ones. Tail sampling (a decision made after the full trace is assembled) costs more memory at the Collector because it must buffer spans while waiting for the trace to complete, but it correctly keeps all traces that match your interest — errors, latency outliers, anomalies — while aggressively dropping healthy fast traces.
For industrial IoT, the recommendation is head sampling disabled (always-sample) at the edge SDK level, with tail sampling at the edge Collector. The edge Collector sees all spans from a single node, so trace assembly is straightforward. A typical policy: keep 100% of error traces, 100% of traces slower than your SLO threshold, and a small probabilistic sample (5–10%) of healthy traces for baseline profiling.
Cardinality Control for Metrics
The risk metric for cardinality is the number of unique label-value combinations (time series) in Prometheus or your metrics backend. An edge deployment that emits per-asset, per-sensor-channel metrics with a naive labeling scheme can create hundreds of thousands of time series per plant — far beyond what most self-hosted Prometheus instances handle well.
Practical controls:
- Use the
filterprocessor to drop metrics you do not query or alert on. If you collect 200 sensor channels but only alert on 20, drop the other 180 at the Collector before export. - Prefer aggregate metrics at the edge — a histogram of all sensor latencies is one time series per histogram bucket, not one per sensor. Aggregate at the edge Collector with the
metricstransformprocessor if needed. - Avoid including unbounded values (serial numbers, job IDs, session IDs) as metric label values. Use them as span/trace attributes instead, where their cardinality is scoped to individual traces rather than inflating global time-series count.
- Set cardinality limits in your metrics backend (Prometheus’s
--storage.tsdb.max-block-durationor Grafana Mimir’s per-tenant limits) as a backstop.
Trade-offs, Gotchas, and What Goes Wrong
OTel for industrial IoT is powerful but not magic. Here are the failure modes that matter in production.
Tail sampling requires trace completeness. The tailsampling processor holds spans in memory until decision_wait expires. If spans from a single trace arrive at two different Collector instances (common once you scale beyond one edge node), the tail sampler sees an incomplete trace and cannot apply latency or error policies correctly. The fix is the loadbalancing exporter, which routes spans by trace_id hash to a consistent backend — but it adds a hop and a failure mode. The simpler rule: use tail sampling only at the tier where a single Collector sees all spans of a trace.
MQTT 3.1.1 context propagation has no clean standard. Embedding traceparent in JSON payload wrappers breaks binary payloads and requires all consumers to understand the wrapper format. The side-channel approach (separate correlation topic) adds complexity and creates synchronization risks. If you control the device firmware, migrate to MQTT 5.0. If you do not, accept partial trace stitching and use span links rather than parent-child relationships for cross-protocol hops.
Offline buffer drain ordering. When a WAN link recovers and the file-buffered spans drain to the gateway, they may arrive out of order relative to spans that were exported live. Backends like Tempo handle out-of-order span ingestion gracefully; Prometheus does not — late-arriving samples are silently dropped if their timestamps fall outside the --out-of-order-time-window. Configure Prometheus’s out-of-order ingestion window to match your expected maximum WAN outage duration.
SDK auto-instrumentation gaps. Auto-instrumentation (Java agent, Python opentelemetry-instrument, Node.js @opentelemetry/auto-instrumentations-node) covers HTTP, gRPC, database clients, and common frameworks automatically, but it does not instrument custom industrial protocols (OPC-UA, Modbus, DNP3, IEC 61850). You must add manual instrumentation for these — which means understanding span semantics deeply enough to create meaningful spans, not just “start span / end span” wrappers.
Clock skew across edge nodes. Distributed traces rely on timestamps being comparable across services. In a plant with air-gapped segments or poorly synced hardware clocks, span timestamps from different nodes can be off by seconds or more, making trace waterfalls misleading. Use NTP or PTP (IEEE 1588) synchronization across all edge nodes that contribute spans to a shared trace.
Cardinality explosion from per-asset labels. As described above, including asset serial numbers or sensor channel IDs as metric label values is the single most common operational mistake in new IIoT OTel deployments. Audit your metric labels before going to production; the filter and attributes processors are your remediation path.
Practical Recommendations
The following recommendations reflect the architecture and trade-offs discussed above.
Start with the Collector, not the SDK. Deploy the OTel Collector in agent mode on every edge node first, configure filelog and prometheus receivers, and begin getting telemetry from existing infrastructure. SDK instrumentation of custom services comes second, once operators trust the pipeline.
Use two-tier Collector topology from day one. A single-tier (edge → cloud) topology is simpler to bootstrap but harder to scale and more fragile under WAN outages. The gateway tier pays for itself the first time the WAN link goes down and the edge Collector continues buffering.
Pin resource attributes in the Collector, not the SDK. New services are added over time; enforcing a consistent site.id / asset.type taxonomy centrally in the Collector’s attributes processor is more reliable than trusting every developer to set them correctly in every SDK initialization.
Instrument context propagation at MQTT boundaries explicitly. Don’t assume it works; add a trace-stitching integration test that publishes a message with a known traceparent, subscribes, and verifies that the child span’s trace_id matches.
For KubeEdge and Kubernetes-at-the-edge deployments, the OTel Collector runs naturally as a DaemonSet — one pod per edge node. The KubeEdge production deep dive covers node-level resource constraints that directly affect Collector pod sizing.
Quick checklist before going live:
memory_limiteris first in every processor chain- TLS between edge and gateway Collectors is enabled
file/offline_bufferis on a persisted volume- Cardinality audit complete — no unbounded label values in metrics
- NTP/PTP clock sync validated on all edge nodes
traceparentpropagation tested end-to-end through every protocol boundary- Explore the Unified Namespace pattern to understand how OTel telemetry data fits into a broader plant-floor data architecture
Frequently Asked Questions
Does OpenTelemetry support MQTT natively?
MQTT is not yet a first-class receiver or propagation target in the upstream OpenTelemetry Collector as of mid-2026. The practical approach is to inject W3C traceparent as an MQTT 5.0 user property on the publisher side and extract it on the subscriber side, as shown in this tutorial. For MQTT 3.1.1, embedding traceparent in a JSON payload envelope is the most common workaround. A custom MQTT receiver can be built using the Collector Builder (ocb), but this requires Go development and maintenance overhead.
What is the difference between head sampling and tail sampling in OpenTelemetry?
Head sampling makes a keep/drop decision at the start of a trace, before any spans are collected, typically based on a fixed probability. This is low-overhead but discards error traces and slow traces at the same rate as healthy ones. Tail sampling makes the decision after the entire trace is assembled, so you can keep 100% of error and slow traces while dropping most healthy fast traces. Tail sampling requires more Collector memory and works best when all spans of a trace arrive at a single Collector instance.
How do I control costs in an OTel industrial IoT deployment?
The two main cost levers are sampling (reducing the number of traces exported) and cardinality control (reducing the number of unique metric label combinations). Use tail sampling at the edge Collector to keep only diagnostically interesting traces. Audit metric labels to remove unbounded values like serial numbers or job IDs. Use the Collector filter processor to drop high-volume, low-value signals before they leave the edge. Size your offline buffer and retention according to your WAN characteristics and backend storage budget.
Can I use OpenTelemetry with legacy OPC-UA or Modbus devices?
Auto-instrumentation does not cover OPC-UA, Modbus, or other industrial protocols. You need to add manual instrumentation in the adapter service that translates between these protocols and your application layer. Wrap each OPC-UA read or Modbus poll in a span, set attributes like rpc.system: "opcua" and the node ID being read, and propagate context through any downstream calls. The OTel semantic conventions for RPC systems provide a useful baseline for attribute naming.
What backends work with the OTel Collector for industrial IoT?
Any backend that accepts OTLP, Prometheus remote write, or Loki push works. A common open-source stack for industrial use is Grafana Tempo (traces), Prometheus or Grafana Mimir (metrics), and Grafana Loki (logs), all visualized in Grafana dashboards. Commercial options include Honeycomb, Datadog, Dynatrace, and New Relic — all accept OTLP natively. The Collector’s exporter model means you can fan out to multiple backends simultaneously, which is useful during a migration from a legacy monitoring tool.
How does OpenTelemetry compare to proprietary industrial monitoring tools?
Proprietary tools like Datadog, Dynatrace, or vendor-specific SCADA monitoring offer faster time-to-value and tighter integration with their own backends, but at the cost of vendor lock-in on the data model and agent. OpenTelemetry’s vendor-neutral data model means you can switch backends without re-instrumenting, and the Collector can fan out to multiple backends simultaneously. The trade-off is higher initial setup effort and the need to build expertise in the OTel ecosystem. For greenfield industrial IoT deployments, OpenTelemetry is increasingly the lower total-cost-of-ownership choice over a 3–5 year horizon.
Further Reading
- eBPF Observability with Pixie and Cilium — complements OTel with automatic network-level visibility, no code changes required
- KubeEdge Production Deep Dive — edge Kubernetes node constraints that directly affect OTel Collector sizing and DaemonSet configuration
- Unified Namespace Architecture — how OTel telemetry integrates with plant-wide data architecture patterns
- OpenTelemetry Official Documentation — the authoritative source for SDK APIs, Collector configuration reference, and semantic conventions
- W3C Trace Context Specification — the standard defining
traceparentandtracestateheaders used for cross-service context propagation
By Riju — about.
