Introduction
Ask five engineers to draw their observability stack and you'll get five different diagrams, each with a different collection of boxes and arrows that somehow all claim to answer the same question: "what is my system doing right now, and why did it just break?" The tool list has grown long enough that picking a stack feels less like an engineering decision and more like assembling furniture from several different manufacturers. Prometheus, Loki, Promtail, OpenTelemetry, Morgan, Winston, Pino, the OTel Collector, ClickHouse, Alloy, Elasticsearch, Kibana, Logstash, Umami, Uptime Kuma, GlitchTip - each name represents a real design decision made by a real team, usually in response to a specific cost or scaling problem with whatever came before it.
This article is an attempt to organize that list into something you can reason about, rather than memorize. Instead of treating every tool as a competitor to every other tool, we'll group them by the job they actually do - producing telemetry, shipping it, storing it, querying it, and the adjacent jobs of catching errors, watching uptime, and measuring traffic - and look at where each one is a genuinely different architectural bet rather than just a different logo. The goal isn't to crown a "best" stack. It's to give you enough context to build one that matches your team's size, budget, and tolerance for operational overhead, with working code and configuration you can adapt directly.
Why Observability Stacks Multiply
Observability is conventionally split into three pillars: metrics (numeric measurements sampled over time), logs (discrete, timestamped event records), and traces (the causal path of a single request across services). Historically each pillar was solved by an entirely separate tool with its own data model, its own agent, and its own query language, because the storage engines needed to make each type of data fast to query are genuinely different - a time-series database optimized for numeric aggregation looks nothing like a full-text search index, which in turn looks nothing like a store optimized for wide, sparse, high-cardinality trace spans. That separation is why a "complete" stack has historically required at least three independent systems even before you add dashboards on top.
The rise of OpenTelemetry has partly closed that gap by giving all three signals a shared instrumentation layer and a shared wire protocol, but it hasn't eliminated the need to choose a backend, and backend choice is still driven by the same forces that created the fragmentation in the first place: query performance at your actual data volume, storage cost per gigabyte retained, and how much operational effort your team can spend running databases instead of shipping features. Commercial platforms like Datadog and New Relic exist precisely to absorb that operational effort, but they charge for it in a way that scales uncomfortably with telemetry volume, which is exactly the pressure that pushes teams toward the self-hosted tools in this list.
That's also why almost every tool named in this piece is open source and self-hostable rather than a SaaS product. Prometheus, Loki, OpenTelemetry, ClickHouse, Elasticsearch, GlitchTip, Uptime Kuma, and Umami all represent the "run it yourself, own the data, control the cost curve" lane of the ecosystem, as opposed to the "pay per event and let someone else run it" lane. That distinction matters more than any individual feature comparison, because it changes who is responsible for capacity planning, upgrades, and the 2 a.m. page when the observability system itself falls over.
Mapping the Three Pillars to This Tool List
With that framing in place, the tool list actually organizes itself fairly cleanly. For metrics, Prometheus remains the default open-source engine, with OpenTelemetry's metrics SDK as the increasingly common way to produce that data and Grafana Alloy as the modern agent that scrapes and forwards it. For logs, the pipeline runs from an application-level logging library - Morgan, Winston, or Pino in a Node.js service - through a shipping agent (historically Promtail, now Alloy) into a storage and query layer that is either Grafana Loki or the Elasticsearch/Logstash/Kibana combination, depending on how much full-text search you actually need. For traces, the OpenTelemetry SDK instruments the code, the OpenTelemetry Collector receives and routes the resulting spans, and ClickHouse increasingly shows up as the storage engine underneath OTel-native platforms that want one fast analytical database instead of three specialized ones.
Outside those three lanes sit three narrower, single-purpose tools that round out a practical self-hosted monitoring setup without pretending to be full observability platforms: GlitchTip for grouping and triaging application errors, Uptime Kuma for external synthetic checks that tell you whether your service is reachable at all, and Umami for privacy-respecting web analytics that has nothing to do with system health but everything to do with understanding how the product is actually used. None of the three tries to replace metrics, logs, or traces - they answer questions those pillars don't, and they're cheap enough in resource terms that most teams can run all three without a second thought.
Metrics Layer: Prometheus, OpenTelemetry Metrics, and Grafana Alloy
Prometheus, originally built at SoundCloud and donated to the Cloud Native Computing Foundation in 2016, established the model that most self-hosted metrics tooling still follows: a pull-based scraper periodically hits an HTTP endpoint exposed by each service, parses a simple text format of labeled numeric samples, and stores the result as time series keyed by metric name plus label set. Its query language, PromQL, is built specifically for rate calculations, percentiles, and alerting thresholds over that data, and its Alertmanager component handles deduplication, grouping, and routing of the alerts those queries produce. The pull model has a real operational advantage: Prometheus, not your application, decides the scrape schedule, which makes it straightforward to reason about load and to detect a dead target simply by noticing scrapes have stopped.
The trade-off is that a single Prometheus server is fundamentally a single node with local disk storage, which caps how much history it can hold and how it survives that node failing. Production deployments typically layer something on top for long-term storage and horizontal scale - Thanos, Cortex, or Grafana Mimir are the common choices - which reintroduces some of the operational complexity Prometheus itself was designed to avoid. This is one of the places where "just run Prometheus" quietly becomes "run Prometheus plus a remote-write-compatible long-term store", and it's worth budgeting for that second piece from the start rather than discovering the retention limit in production.
OpenTelemetry's metrics API offers a push-based alternative that lives inside your application code rather than requiring a scrape endpoint, and it uses the same instrumentation and export pipeline as OTel traces and logs, which means one SDK produces all three signals with consistent resource attributes. A minimal counter and histogram setup in a Node.js service looks like this:
import { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
import { Resource } from '@opentelemetry/resources';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
const exporter = new OTLPMetricExporter({
url: 'http://otel-collector:4318/v1/metrics',
});
const meterProvider = new MeterProvider({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'checkout-service',
}),
readers: [new PeriodicExportingMetricReader({ exporter, exportIntervalMillis: 15000 })],
});
const meter = meterProvider.getMeter('checkout-service');
const orderCounter = meter.createCounter('orders_processed_total', {
description: 'Number of orders successfully processed',
});
const orderLatency = meter.createHistogram('order_processing_duration_ms', {
description: 'Time taken to process an order end-to-end',
});
export function recordOrder(durationMs: number, status: 'success' | 'failed') {
orderCounter.add(1, { status });
orderLatency.record(durationMs, { status });
}
Grafana Alloy is the piece that increasingly sits underneath both models. It's Grafana Labs' unified telemetry agent, built as a distribution of the OpenTelemetry Collector, and it replaces two older Grafana tools at once: Grafana Agent for metrics scraping and Promtail for log tailing. That consolidation isn't optional for much longer - Promtail entered long-term support in February 2025 and reached end of life on March 2, 2026, meaning it no longer receives updates or security fixes, and Grafana explicitly directs remaining users to Alloy, which ships an alloy convert --source-format=promtail command specifically to translate existing Promtail configurations. If your stack still lists Promtail, the practical next step isn't evaluating it against alternatives - it's migrating off it.
Logs Layer: Application Loggers and Aggregators
Before a log line can be shipped anywhere, something inside the application has to produce it, and this is where Morgan, Winston, and Pino enter the picture as three different answers to the same Node.js problem. Morgan is HTTP request logging middleware for Express: it sits in the request pipeline and emits one line per request in a configurable format (combined, dev, or a custom token string), which makes it excellent for quick access-log-style visibility but limited as a general-purpose logger, since it only knows about HTTP requests and doesn't naturally produce structured, machine-parseable output without extra work.
Winston and Pino both solve the broader problem of application-wide structured logging, but they optimize for different things. Winston is built around flexibility: multiple simultaneous "transports" (console, file, HTTP, cloud sinks), configurable formatters, and log levels, which makes it a comfortable default when you need one logger to fan out to several destinations. Pino is built around throughput: it produces newline-delimited JSON with minimal serialization overhead, which matters once logging volume is high enough that the logger itself becomes a measurable cost on the request path, and it pairs with pino-http as a structured, Morgan-equivalent request logger. The practical difference shows up in code as much as in benchmarks:
// Winston: flexible, multi-transport, good default for moderate volume
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'app.log' }),
],
});
logger.info('order processed', { orderId: 'ord_123', amount: 49.99 });
// Pino: minimal overhead, JSON-only, designed for high-throughput services
const pino = require('pino');
const logger2 = pino({ level: 'info' });
logger2.info({ orderId: 'ord_123', amount: 49.99 }, 'order processed');
Once logs are structured JSON on disk or stdout, the question becomes how they get aggregated and searched, and this is where the two paths from the metrics discussion reappear. The lightweight path ships logs through an agent - Promtail historically, Alloy now - into Grafana Loki, which was deliberately designed to index only a small set of labels rather than the full text of every line, keeping storage cost low at the expense of needing to scan raw log content at query time via LogQL. The heavier path ships logs into Logstash for parsing and enrichment and then into Elasticsearch, which indexes full text via Lucene and supports rich ad hoc search, at a meaningfully higher storage and compute cost per gigabyte of logs retained.
Traces Layer: OpenTelemetry SDK and the Collector
Distributed tracing solves a problem that logs and metrics can't: when a single user-facing request touches a dozen internal services, something has to stitch those dozen sets of logs and metrics back into one causal timeline. That's what a trace does - a single trace ID propagated through context headers, with each service contributing one or more spans that record how long its portion of the work took and how it relates to the spans before and after it. Getting this right requires instrumentation inside every service in the request path, which is precisely what the OpenTelemetry SDK provides: language-specific packages that either auto-instrument common frameworks (HTTP servers, database clients, message queues) or expose an API for manual span creation where automatic coverage isn't enough.
A typical Node.js service picks up most of this instrumentation with very little custom code, because the OTel ecosystem ships auto-instrumentation packages for the popular frameworks:
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { Resource } from '@opentelemetry/resources';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
const sdk = new NodeSDK({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'checkout-service',
[SemanticResourceAttributes.SERVICE_VERSION]: process.env.GIT_SHA ?? 'dev',
}),
traceExporter: new OTLPTraceExporter({ url: 'http://otel-collector:4318/v1/traces' }),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
process.on('SIGTERM', () => sdk.shutdown().finally(() => process.exit(0)));
Rather than exporting straight from every service to a backend, most production setups route through the OpenTelemetry Collector, a standalone process that sits between instrumented applications and whatever storage you've chosen. The Collector's configuration is built from three kinds of components wired into pipelines: receivers accept incoming telemetry (typically OTLP over gRPC or HTTP), processors transform it in flight (batching, sampling, attribute scrubbing for sensitive fields), and exporters send the result onward, often to more than one destination at once. Centralizing that logic in one place means you can change sampling rates or redact a field across your entire fleet by editing one configuration file instead of redeploying every service.
OpenTelemetry's position in this stack got considerably more solid in 2026: the project reached CNCF Graduated status in May 2026, and by that point all three core signals - tracing, metrics, and logs - had reached general-availability, stable status across the major language SDKs, with continuous profiling emerging as a fourth signal still moving through the specification process. For a team picking instrumentation today, that maturity is the practical argument for standardizing on OTel regardless of which backend you eventually choose: the instrumentation code in your services doesn't need to change when the backend does.
Storage and Query Backends: ELK vs Loki vs ClickHouse
The Elastic stack - Elasticsearch for storage and full-text indexing, Logstash for ingestion and transformation, Kibana for visualization - remains the most feature-complete option for genuine full-text log search, because it's built on Lucene and indexes every field by default. That completeness has a cost: indexing everything is expensive in both disk and CPU at high log volume, and Logstash in particular has a reputation for being resource-heavy relative to lighter shipping agents. The stack's licensing has also shifted more than once - Elastic moved Elasticsearch and Kibana away from Apache 2.0 to the Server Side Public License and the Elastic License in 2021, which is what prompted AWS to fork the pre-license codebase into OpenSearch, and Elastic subsequently reintroduced AGPL as a third licensing option in 2024. Anyone standardizing on Elasticsearch today should read the current license terms directly rather than relying on what was true a few years ago.
Grafana Loki takes the opposite bet: rather than indexing log content, it indexes only a small set of labels (service name, environment, and similar low-cardinality fields) and stores the raw log lines in cheap object storage, querying them with LogQL at read time. The result is dramatically lower ingestion and storage cost for the same log volume, at the price of slower ad hoc full-text search - Loki is genuinely described as "Prometheus, but for logs", and that framing extends to its label-cardinality sensitivities, which mirror the cardinality problems Prometheus users already know to watch for.
ClickHouse approaches the problem from a different angle entirely: it's a general-purpose columnar OLAP database, not a purpose-built observability tool, but its combination of vectorized query execution and heavy compression makes it fast enough at aggregate queries over large, high-cardinality datasets that a growing set of OpenTelemetry-native platforms - SigNoz, Uptrace, and Highlight.io among them - use it as a single unified backend for traces, metrics, and logs together, rather than running three specialized stores. The trade-off shows up operationally rather than in query performance: running ClickHouse well at observability ingest rates means managing merge pressure from continuous streaming inserts and coordinating a sharded, replicated cluster, which is a different skill set than operating Elasticsearch or Loki even though the end result - one queryable store for all your telemetry - is genuinely attractive.
Beyond Core Telemetry: Errors, Uptime, and Web Analytics
GlitchTip fills a gap that neither logs nor traces are well suited to: grouping the same underlying exception across thousands of occurrences into one trackable issue with stack traces, breadcrumbs, and release metadata attached. It's built on Django and deliberately speaks the same wire protocol as Sentry's SDKs, so migrating an existing codebase means changing the DSN endpoint the Sentry client points to rather than rewriting instrumentation - a compatibility decision that's largely why teams reach for it. It's noticeably lighter than a full Sentry deployment, both in the resources it needs to self-host and in the feature set it exposes; it doesn't attempt session replay or deep performance profiling, and treating it as a focused error tracker rather than a Sentry replacement in every dimension sets the right expectations.
That focus is a feature, not a gap. A dedicated error tracker answers a specific question - "which bug is hurting the most users right now, and has it happened before?" - that's awkward to answer by grepping structured logs or scanning traces, because the value comes from grouping and deduplication logic that a general-purpose store doesn't provide out of the box. Running GlitchTip alongside a metrics and tracing stack isn't redundant; it's answering a question the other two pillars weren't designed to answer well.
Uptime Kuma occupies an entirely different position in the stack: it's an external, synthetic monitor, checking whether your service is reachable at all via HTTP, TCP, ping, DNS, or a handful of other protocols, independent of anything your own instrumentation reports. That independence is the point - if your OTel Collector, your Prometheus server, and your application are all down at once, internal telemetry tells you nothing, but an external checker watching from outside your infrastructure will still notice and alert. It ships as a single self-hostable container with a built-in status page and integrations for the usual notification channels, which is why it's become a common lightweight addition even to teams that already have heavier internal monitoring.
Umami sits furthest from the "observability" label of the group, but it solves a real adjacent problem: understanding how a product is actually used without routing that data through a third-party advertising platform. It's a cookie-less, privacy-focused web analytics tool, MIT-licensed and self-hostable on a small Node.js and PostgreSQL footprint, built specifically to avoid the personal-data collection that makes GDPR consent banners necessary in the first place. Teams that care about data sovereignty for operational telemetry tend to care about the same thing for product analytics, which is why Umami often ends up in the same infrastructure conversation even though it's answering a product question rather than a reliability one.
Practical Implementation: A Reference Architecture
Putting the pieces together, a coherent self-hosted stack for a small-to-mid-size engineering team might look like this: application services instrument themselves with the OpenTelemetry SDK for traces and metrics, and use Pino or Winston for structured application logs depending on throughput needs. Grafana Alloy runs as a node-level agent, scraping Prometheus-style metrics endpoints and tailing log files, while also accepting OTLP directly from services that push rather than expose a scrape endpoint. Everything funnels into the OpenTelemetry Collector, which fans data out to Prometheus for metrics, Loki or a ClickHouse-backed store for logs and traces, with Grafana on top as the single pane of glass for all three. GlitchTip catches unhandled exceptions from the same services, Uptime Kuma watches the public endpoints from outside the cluster, and Umami sits on the marketing site collecting traffic data no one else gets to see.
The Collector configuration is the piece that makes this fan-out actually work, since it's the one place where a single incoming stream of telemetry gets routed to multiple, architecturally different backends:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 5s
attributes:
actions:
- key: http.request.header.authorization
action: delete
exporters:
prometheusremotewrite:
endpoint: "http://prometheus:9090/api/v1/write"
loki:
endpoint: "http://loki:3100/loki/api/v1/push"
clickhouse:
endpoint: "tcp://clickhouse:9000"
database: otel
traces_table_name: otel_traces
logs_table_name: otel_logs
service:
pipelines:
metrics:
receivers: [otlp]
processors: [batch]
exporters: [prometheusremotewrite]
logs:
receivers: [otlp]
processors: [batch, attributes]
exporters: [loki, clickhouse]
traces:
receivers: [otlp]
processors: [batch, attributes]
exporters: [clickhouse]
Running this in production means budgeting real attention for retention and sizing rather than treating it as a set-and-forget deployment: Prometheus and Loki both need explicit retention windows configured or they'll happily fill a disk, ClickHouse needs its merge and TTL settings tuned to the ingest rate you're actually generating, and someone on the team needs to own the Collector configuration the same way someone owns application code, since a misconfigured processor or a dropped exporter silently blinds part of the stack without necessarily throwing an obvious error.
Trade-offs and Common Pitfalls
The most consistent failure mode with a self-hosted stack this broad isn't any single tool underperforming - it's the cumulative operational tax of running six or more stateful systems that each need upgrades, backups, and capacity planning, on top of the application infrastructure the team is already responsible for. This is the real trade against SaaS observability platforms: it's not that Prometheus or Loki are worse at their job, it's that someone has to be on call for them, and that person is now you instead of a vendor's SRE team. Teams that skip this accounting tend to discover it during an incident, when the observability stack itself becomes the thing that's down.
Several of these tools also carry specific, well-documented failure modes worth knowing before you hit them in production rather than during an incident. Loki and Prometheus both degrade badly under high label cardinality - a label that takes on thousands of distinct values (a raw user ID, for instance) can quietly blow up storage and query latency in either system. Elasticsearch has an analogous problem with mapping explosions when unstructured JSON fields are indexed without a defined schema. ClickHouse-backed platforms face a different class of issue: continuous streaming inserts create merge pressure, and deletes or updates run as asynchronous mutations rather than immediate operations, both of which need active tuning at meaningful ingest volume rather than default configuration.
Promtail's end-of-life is worth treating as its own case study rather than a footnote, because it's a clean example of a risk that applies to open-source tooling generally: even widely adopted, actively-used software can be deprecated by its maintainer with a hard EOL date, and betting an entire log pipeline on one small, single-purpose agent means inheriting that migration cost on someone else's timeline. Grafana gave over a year of long-term support before EOL and shipped an automated config converter, which is about as graceful a deprecation as this kind of transition gets - but the lesson generalizes: know which pieces of your stack are actively maintained versus coasting in maintenance mode, and don't be surprised when coasting mode has an expiration date.
Finally, the application-level logging and instrumentation choices carry their own quieter risks. Pino and Winston will happily log at whatever volume your code asks them to, and without sampling or rate limiting, a noisy error loop can produce enough log volume to meaningfully affect both cost and query performance downstream. OpenTelemetry's auto-instrumentation is convenient, but instrumenting every database call and HTTP request without a sane trace sampling ratio can add measurable per-request overhead and generate far more span data than any dashboard actually needs to be useful.
Best Practices for Building This Stack
Standardize instrumentation on OpenTelemetry rather than a backend-specific agent or SDK wherever you have the choice, because it's the one decision in this whole list that keeps your options open later. Application code that emits OTLP doesn't care whether it's ultimately stored in Prometheus, Loki, ClickHouse, or a commercial platform, which means a change in backend strategy - moving from Elasticsearch to a ClickHouse-backed platform, for instance - becomes a Collector configuration change instead of a re-instrumentation project across every service.
Deliberately separate the "cheap, label-indexed" path from the "expensive, full-text-indexed" path instead of routing everything through one system by default. Structured JSON logs with a small set of consistent labels belong in Loki or a ClickHouse-backed store, where cost scales reasonably with volume; reserve Elasticsearch's full-text indexing for the specific logs where ad hoc, unstructured search genuinely earns its cost, such as compliance or security-relevant audit trails where you don't know in advance what you'll need to search for.
Resist the temptation to treat GlitchTip, Uptime Kuma, and Umami as consolidation targets for a future "everything platform". Their value is precisely that each one does one job well with a small footprint, and the moment a team starts asking GlitchTip to do APM or Uptime Kuma to do deep internal tracing, it's usually a sign they've outgrown the tool's actual purpose rather than a sign the tool needs new features. When that happens, it's a legitimate trigger to add a genuinely different tool for that job rather than stretching an existing one past its design intent.
Mental Model: Observability as a Nervous System
A useful way to hold all of this in your head at once is to think of the three pillars as different parts of a biological nervous system rather than as competing products. Metrics are like vital signs - heart rate, temperature, blood pressure - cheap to sample continuously, numeric, and good at telling you something is wrong before you know exactly what. Logs are like detailed nerve signals from a specific location: verbose, situational, and most useful once you already know roughly where to look. Traces are the reflex arc itself, the actual path a signal took from stimulus to response across the whole system, which is why they're the only pillar that can answer "which of these twelve services actually caused the slow request."
Extending the analogy, the OpenTelemetry Collector functions like the spinal cord: a central routing point where every signal from every part of the body passes through before it reaches the brain (your storage and dashboards), which is exactly why it's the right place to filter, batch, or scrub sensitive information rather than trying to do that at every individual nerve ending. GlitchTip, Uptime Kuma, and Umami are more like specialized external senses - pain receptors, a mirror, and a notebook, respectively - genuinely useful, but clearly distinct from the core nervous system rather than competing with it for the same job.
The 80/20 Insight
If you strip away every specific tool name, the disproportionate value in this entire stack comes from three ideas, and most of the practical benefit of "doing observability well" comes from getting these three right rather than from picking the perfect product in every category.
First: structured logging with a small, deliberately chosen set of labels, applied consistently across every service, which is what makes both Loki-style cheap indexing and Elasticsearch-style full-text search actually usable later - unstructured or inconsistently-labeled logs undermine every tool downstream of them regardless of which one you pick.
Second: OpenTelemetry instrumentation as the universal producer, regardless of backend. The specific query language or storage engine you land on matters far less than whether your services emit a consistent, semantically-conventioned stream of telemetry that can be redirected without a re-instrumentation project - this is the single decision most likely to save you a costly migration two years from now.
Third: the Collector (or Alloy) as the one deliberate chokepoint for sampling, redaction, and routing policy, rather than scattering that logic across every service's individual configuration, which is what keeps a fan-out architecture like the one in this article maintainable instead of becoming seventeen slightly different configurations that drift out of sync.
In practice, that suggests a sequencing: get structured logs and OTel-based metrics instrumented first, since they're cheap and immediately useful even with a single backend. Add tracing once you actually have more than a couple of services calling each other, since a single-service trace tells you almost nothing a log line didn't already. Hold off on a dedicated error tracker or uptime monitor until you notice you're manually doing the job they'd automate - grepping logs for the same exception repeatedly, or finding out about outages from customers instead of from a monitor - since adding either earlier than that is optimization before you've felt the pain it solves.
Key Takeaways
None of this requires adopting all seventeen tools on day one, and trying to would likely produce more operational risk than insight. The point of laying the landscape out this way is to make the next decision - what to add when the current setup starts hurting - an informed one instead of a reactive one. The five steps below are roughly the order most teams actually move through as their systems and team size grow, and each one is something you can start on this week rather than something that requires a full platform migration.
- Instrument with OpenTelemetry from day one, even if you're initially exporting only to Prometheus - it keeps every future backend change a configuration problem, not a code change.
- Standardize log structure and label sets across services before choosing between Loki and Elasticsearch; a consistent schema is worth more than either tool's specific feature set.
- Put an OpenTelemetry Collector (or Alloy) between your services and any backend, even a simple one, so you have a single place to add sampling or redaction later without touching application code.
- Add tracing once you have real cross-service calls to follow, not before - a trace across one service adds overhead without adding insight.
- Bring in GlitchTip, Uptime Kuma, or Umami individually, only when you feel the specific gap each one fills - grouped errors, external reachability, or privacy-respecting analytics - rather than as a bundled starter kit.
Conclusion
The seventeen names in this list aren't really competing for the same slot in your architecture - they're mostly answering different questions at different layers, and the confusion comes from treating "observability tool" as one category when it's really at least six. Prometheus and OpenTelemetry metrics answer "what are the numbers doing." Morgan, Winston, Pino, Alloy, Loki, and the Elastic stack answer "what happened, in detail, and can I search it." The OTel SDK, the Collector, and ClickHouse-backed platforms answer "how did this specific request move through my system." GlitchTip, Uptime Kuma, and Umami answer three narrower questions that don't fit neatly into any of the above but matter anyway.
What ties the useful versions of this stack together isn't any single product choice but the connective tissue: OpenTelemetry as a shared instrumentation and transport layer, and a deliberate collector stage as the one place where routing and policy decisions live. Get those two decisions right and the specific storage engines underneath - Loki versus Elasticsearch, Prometheus versus a remote-write backend, ClickHouse versus a purpose-built trace store - become swappable implementation details rather than irreversible architectural commitments.
There's no version of this stack that's correct independent of context. A three-person startup and a two-hundred-engineer platform team should not end up with the same set of boxes on the diagram, and the right move for either one is less about matching a reference architecture from a blog post and more about being honest about current pain, current team size, and current tolerance for running stateful infrastructure. Use the groupings here as a map, not a shopping list, and add the next piece only when the gap it fills is one you've actually felt.
References
- Prometheus documentation, "Overview" and "Querying" - prometheus.io/docs
- Grafana Labs, "Loki: like Prometheus, but for logs" - grafana.com/docs/loki
- Grafana Labs, "Migrate from Promtail to Grafana Alloy" and Promtail end-of-life notice - grafana.com/docs/alloy/latest/set-up/migrate/from-promtail
- OpenTelemetry, "Logs" concept documentation and specification status summary - opentelemetry.io/docs/concepts/signals/logs
- OpenTelemetry blog, "OpenTelemetry Has Graduated… Now what?" - opentelemetry.io/blog/2026/otel-grad-now-what
- CNCF, "OpenTelemetry has graduated… Now what?" - cncf.io/blog
- ClickHouse, "Building an Observability Solution with ClickHouse - Traces" - clickhouse.com/blog
- Elastic, Elasticsearch, Logstash, and Kibana product documentation - elastic.co/guide
- Winston GitHub repository and README - github.com/winstonjs/winston
- Pino GitHub repository and README - github.com/pinojs/pino
- Morgan GitHub repository and README - github.com/expressjs/morgan
- Umami documentation - umami.is
- Uptime Kuma GitHub repository - github.com/louislam/uptime-kuma
- GlitchTip documentation and GitHub repository - glitchtip.com and gitlab.com/glitchtip
- CNCF Cloud Native Landscape - landscape.cncf.io