Оглавление
- Executive summary
- Исходные допущения для статьи
- Что такое OpenTelemetry и как устроен стек
- Как OpenTelemetry соотносится с Prometheus, Grafana, Loki, Jaeger и Zipkin
- Практическая реализация
- Как строить корреляцию между трассами, метриками и логами
- Best practices для production
- Стадии внедрения, типичные ошибки и чеклист миграции
- Открытые вопросы и ограничения
Executive summary
OpenTelemetry имеет смысл подавать в статье не как «ещё один мониторинг-инструмент», а как единый стандарт генерации, передачи и первичной обработки телеметрии. Его главная практическая ценность для стека Kingservers — не в том, что он заменяет Prometheus, Loki, Jaeger или Grafana, а в том, что он даёт общий контекст для всех сигналов: единые resource-атрибуты, единые semantic conventions, единый протокол OTLP и единый pipeline через Collector. Именно поэтому связка OpenTelemetry + Prometheus + Loki + Jaeger + Grafana логично выглядит как эволюция уже знакомого LGJL/LGTM-подхода, а не как конкурирующая идеология.Для практической статьи на русском сильнее всего работает такой тезис: OpenTelemetry — это слой стандартизации и корреляции, а Prometheus/Loki/Jaeger — слои хранения и анализа по конкретным сигналам, Grafana — единый интерфейс исследования. Prometheus остаётся системой метрик и time-series, Loki — логовым хранилищем с индексом по labels, Jaeger — бэкендом распределённой трассировки, а Grafana умеет связывать их между собой через exemplars, trace-to-logs и derived fields. На практике рекомендуемая схема для self-hosted и hybrid-инфраструктуры выглядит так: приложения инструментируются SDK или auto-instrumentation, отправляют телеметрию по OTLP в OpenTelemetry Collector, а Collector уже fan-out’ит данные в подходящие хранилища: метрики — в Prometheus-совместимую схему, логи — в Loki через OTLP/HTTP, трейсы — в Jaeger по OTLP. Это уменьшает количество агентов и точек интеграции, упрощает маршрутизацию, sampling, redaction, auth и rollout новых бэкендов. Если делать акцент статьи на корреляции, то ключевой практический совет такой: trace_id не должен жить как label в Loki и не должен становиться обычным metric label в Prometheus. Для логов trace_id лучше сохранять как поле или structured metadata, а для метрик использовать exemplars и общие resource-атрибуты вроде service.name, service.version, service.namespace, deployment.environment.name. Иначе observability-стек быстро упрётся в cardinality, стоимость хранения и медленные запросы. Наконец, в статье стоит сразу зафиксировать две рамки: в исходном ТЗ не указан язык примеров и не указана целевая инфраструктура, поэтому ниже я даю оба варианта — Go и Python — и использую нейтральную self-hosted топологию, которую легко перенести в Docker Compose, VM или Kubernetes. Редакционные ожидания по структуре и практическому характеру материала учтены из приложенного ТЗ.
Готовы перейти на современную серверную инфраструктуру?
В King Servers мы предлагаем серверы как на AMD EPYC, так и на Intel Xeon, с гибкими конфигурациями под любые задачи — от виртуализации и веб-хостинга до S3-хранилищ и кластеров хранения данных.
- S3-совместимое хранилище для резервных копий
- Панель управления, API, масштабируемость
- Поддержку 24/7 и помощь в выборе конфигурации
Результат регистрации
...
Создайте аккаунт
Быстрая регистрация для доступа к инфраструктуре
Исходные допущения для статьи
Так как язык примеров в брифе не задан, разумно не «выбирать за читателя», а прямо сказать в статье: ниже приведены примеры на Go и Python, потому что оба стека официально документированы OpenTelemetry и часто встречаются в backend-инфраструктуре. При этом важно честно отметить различие зрелости логового сигнала: в Go официальная документация прямо помечает logs как experimental, а в Python manual instrumentation docs пишут, что logs API & SDK всё ещё в разработке, хотя практические SDK и exporters уже доступны. Это не повод избегать логов, но это повод для аккуратной формулировки в продакшн-рекомендациях. Точно так же не задана и целевая инфраструктура. Для блога Kingservers логично строить основной narrative вокруг self-hosted или hybrid-схемы: приложение отправляет OTLP в локальный или региональный Collector, а дальше Collector работает как agent или gateway. Официальная документация Collector отдельно описывает оба режима, причём gateway-паттерн рекомендован как централизованная точка агрегации на кластер, дата-центр или регион. Практически это означает, что статья может быть написана как продолжение материалов про Prometheus/Grafana/Loki: сначала читатель уже понимает, где живут метрики, логи и дашборды, а затем OpenTelemetry вводится как слой, который приводит все сигналы к общему словарю и общему пути доставки. Такой угол лучше работает, чем «переучивание» аудитории на полностью новый observability-стек.

Что такое OpenTelemetry и как устроен стек
OpenTelemetry по спецификации разделяет стек на несколько уровней: API, SDK, Semantic Conventions и Data/OTLP. API — это кросс-языковые интерфейсы, которые используют библиотеки и код приложения для создания telemetry; SDK — конкретная реализация API, которой уже управляет владелец приложения; semantic conventions — общий словарь имён и атрибутов; OTLP — протокол передачи телеметрии между SDK, Collector и бэкендами. Спецификация отдельно подчёркивает, что instrumentation authors не должны зависеть от SDK напрямую, а должны зависеть только от API.
Collector — отдельный, но критически важный слой. Он не генерирует телеметрию сам по себе, а получает, обрабатывает и экспортирует её через pipelines из receivers, processors и exporters; при этом один и тот же receiver может участвовать в нескольких pipelines, а один exporter — обслуживать fan-out в несколько направлений. Практически это делает Collector центральной точкой для rate limiting, sampling, redaction, attribute enrichment, tenant routing и миграции между бэкендами без переписывания приложений.Ниже — простая архитектурная схема, которую удобно ставить перед практической частью (показывает роли SDK → Collector → backends и то, что Grafana — только UI).
Архитектура (схема)
flowchart LR
subgraph Apps["Приложения и сервисы"]
A["Service A\nSDK / auto-instrumentation"]
B["Service B\nSDK / auto-instrumentation"]
C["Worker / Cron\nSDK / auto-instrumentation"]
end
A -->|OTLP gRPC/HTTP| OCOL["OpenTelemetry Collector"]
B -->|OTLP gRPC/HTTP| OCOL
C -->|OTLP gRPC/HTTP| OCOL
subgraph Pipeline["Collector pipelines"]
R["Receivers"]
P["Processors\nmemory_limiter / batch / sampling / attributes"]
E["Exporters"]
R --> P --> E
end
OCOL --> Pipeline
E --> PROM["Prometheus"]
E --> LOKI["Loki"]
E --> JAEGER["Jaeger"]
GRAF["Grafana"] --> PROM
GRAF --> LOKI
GRAF --> JAEGER
Эта схема соответствует тому, как OpenTelemetry описывает Collector как vendor-agnostic слой приёма/обработки/экспорта, а Grafana — как UI, который читает данные из специализированных хранилищ, а не принимает телеметрию напрямую.

Как OpenTelemetry соотносится с Prometheus, Grafana, Loki, Jaeger и Zipkin
Главная мысль для блога: OpenTelemetry и перечисленные инструменты не лежат на одном уровне абстракции. Prometheus, Loki, Jaeger и Zipkin — это mainly backends или специализированные observability-системы по одному сигналу. OpenTelemetry — это стандарт инструментирования, контекста и транспорта. Поэтому вопрос нужно ставить не «что лучше», а «какая роль у каждого слоя в общей схеме». Инструмент Основная роль Нативные сигналы Меняет ли OpenTelemetry этот инструмент Практический вывод Источник
OpenTelemetryInstrumentation, context propagation, collection, export Traces, metrics, logs Нет, это стандарт и pipeline Нужен как общий слой и единая схема доставки Prometheus Monitoring system и time-series DB Metrics Не заменяется; может получать OTel-метрики Остаётся лучшим выбором для time-series, alerting и PromQL Grafana Визуализация и investigation UI Не хранит сигналы сам по себе Не заменяется Это слой исследования и корреляции поверх backends Loki Логовое хранилище с индексом по labels Logs Не заменяется Хорошо сочетается с OTel Logs через OTLP/HTTP Jaeger Distributed tracing backend Traces Не заменяется Хороший trace backend, сейчас нативно принимает OTLP Zipkin Distributed tracing backend Traces Не заменяется Более узкий trace-only backend, уместен как альтернатива Jaeger Есть и несколько особенно важных нюансов, которые стоит прямо проговорить в статье. Во-первых, Grafana не является exporter target. Collector экспортирует данные не «в Grafana», а в Prometheus, Loki, Jaeger или другой backend, который Grafana затем использует как datasource. Это кажется очевидным инженерам, но в блоговых материалах эту грань часто размывают. Во-вторых, Jaeger и Zipkin сегодня лучше воспринимать как trace backends, а не как систему observability “в целом”. Jaeger официально пишет, что принимает только trace data по OTLP, а Zipkin остаётся системой распределённой трассировки со span-centric архитектурой. Поэтому в статье корректнее сравнивать их с trace-составляющей OTel-стека, а не с OpenTelemetry целиком. В-третьих, OTLP стал настолько важным, что native Jaeger exporter в Collector больше не является рекомендованным путём. Официальный блог OpenTelemetry прямо объясняет, что Jaeger exporters в Collector убрали из последних бинарных релизов, потому что Jaeger «из коробки» поддерживает OTLP; рекомендуемый путь — обычный OTLP exporter. Это важный practical detail: не показывайте в статье устаревший jaeger exporter для Collector, если пишете современный материал.
Практическая реализация
Базовая схема для self-hosted стека Для статьи Kingservers разумно показать минимальную, но production-похожую топологию: приложение отправляет traces, metrics и logs по OTLP в Collector;
Collector отдаёт метрики в Prometheus-совместимом виде;
Collector отправляет логи в Loki через otlphttp;
Collector отправляет трейсы в Jaeger по OTLP;Grafana подключается ко всем трём источникам как к datasources.
Конфигурация OpenTelemetry Collector (пример)
Ниже — рабочий пример: один OTLP receiver, базовые processors, exporter в Prometheus для scrape, otlphttp в Loki и OTLP gRPC в Jaeger.
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
memory_limiter:
check_interval: 1s
limit_mib: 1024
spike_limit_mib: 256
batch:
timeout: 5s
send_batch_size: 4096
attributes/common:
actions:
- key: deployment.environment.name
action: upsert
value: production
exporters:
prometheus:
endpoint: "0.0.0.0:9464"
enable_open_metrics: true
resource_to_telemetry_conversion:
enabled: true
otlp/jaeger:
endpoint: jaeger:4317
tls:
insecure: true
otlphttp/loki:
endpoint: http://loki:3100/otlp
extensions:
health_check: {}
service:
extensions: [health_check]
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, attributes/common, batch]
exporters: [otlp/jaeger]
metrics:
receivers: [otlp]
processors: [memory_limiter, attributes/common, batch]
exporters: [prometheus]
logs:
receivers: [otlp]
processors: [memory_limiter, attributes/common, batch]
exporters: [otlphttp/loki]
Почему именно так: memory_limiter ставят первым, чтобы Collector успевал включать backpressure и не падал по памяти. batch лучше держать после возможного drop/filter/sampling. Для Loki в OTLP-режиме используется otlphttp exporter на endpoint /otlp, а для Jaeger в современных схемах достаточно OTLP exporter, потому что Jaeger нативно принимает OTLP.
Мини-дополнения в Loki и Prometheus
limits_config:
allow_structured_metadata: true
scrape_configs:
- job_name: otel-collector
scrape_interval: 15s
static_configs:
- targets: ["otel-collector:9464"]
Если хочется показать более «новый» путь для метрик: Prometheus умеет принимать OTel metrics напрямую через OTLP endpoint, но для первой практической статьи проще и привычнее оставить scrape-экспортёр или remote write.

Пример на Go (короткий, но цельный)
Ниже пример, который задаёт propagators, поднимает providers и пишет span/metric/log в одном request context. Для продакшна важны version pin и контроль объёма логов.
package main
import (
"context"
"errors"
"log/slog"
"net/http"
"os"
"os/signal"
"time"
"go.opentelemetry.io/contrib/bridges/otelslog"
"go.opentelemetry.io/contrib/exporters/autoexport"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
otelglobal "go.opentelemetry.io/otel/log/global"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/propagation"
sdklog "go.opentelemetry.io/otel/sdk/log"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
var (
tracer = otel.Tracer("kingservers.checkout")
meter = otel.Meter("kingservers.checkout")
logger *slog.Logger
requestCounter metric.Int64Counter
)
func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
shutdown, err := setupOTel(ctx)
if err != nil { panic(err) }
defer func() { _ = shutdown(context.Background()) }()
http.HandleFunc("/checkout", checkoutHandler)
_ = http.ListenAndServe(":8080", nil)
}
func setupOTel(ctx context.Context) (func(context.Context) error, error) {
var shutdownFns []func(context.Context) error
shutdown := func(ctx context.Context) error {
var err error
for _, fn := range shutdownFns { err = errors.Join(err, fn(ctx)) }
return err
}
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
))
spanExporter, err := autoexport.NewSpanExporter(ctx)
if err != nil { return shutdown, err }
tracerProvider := sdktrace.NewTracerProvider(sdktrace.WithBatcher(spanExporter))
otel.SetTracerProvider(tracerProvider)
shutdownFns = append(shutdownFns, tracerProvider.Shutdown)
metricReader, err := autoexport.NewMetricReader(ctx)
if err != nil { return shutdown, err }
meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(metricReader))
otel.SetMeterProvider(meterProvider)
shutdownFns = append(shutdownFns, meterProvider.Shutdown)
logExporter, err := autoexport.NewLogExporter(ctx)
if err != nil { return shutdown, err }
loggerProvider := sdklog.NewLoggerProvider(sdklog.WithProcessor(sdklog.NewBatchProcessor(logExporter)))
otelglobal.SetLoggerProvider(loggerProvider)
shutdownFns = append(shutdownFns, loggerProvider.Shutdown)
logger = otelslog.NewLogger("kingservers.checkout")
requestCounter, err = meter.Int64Counter(
"http.server.requests",
metric.WithDescription("Total checkout requests"),
metric.WithUnit("{request}"),
)
if err != nil { return shutdown, err }
return shutdown, nil
}
func checkoutHandler(w http.ResponseWriter, r *http.Request) {
ctx, span := tracer.Start(r.Context(), "checkout")
defer span.End()
attrs := []attribute.KeyValue{
attribute.String("http.method", r.Method),
attribute.String("http.route", "/checkout"),
attribute.String("service.name", "checkout-api"),
}
requestCounter.Add(ctx, 1, metric.WithAttributes(attrs...))
logger.InfoContext(ctx, "checkout started", "route", "/checkout")
time.Sleep(120 * time.Millisecond)
span.SetAttributes(attrs...)
logger.InfoContext(ctx, "checkout finished", "status", 200)
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
}
export OTEL_SERVICE_NAME=checkout-api
export OTEL_RESOURCE_ATTRIBUTES=service.version=1.4.3,deployment.environment.name=production
export OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
export OTEL_TRACES_EXPORTER=otlp
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
Пример на Python (ручная инициализация OTLP/HTTP)
import logging
import time
from opentelemetry import trace, metrics
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
from opentelemetry._logs import set_logger_provider
resource = Resource.create({
"service.name": "checkout-api",
"service.version": "1.4.3",
"deployment.environment.name": "production",
})
# Traces
trace_provider = TracerProvider(resource=resource)
trace_provider.add_span_processor(
BatchSpanProcessor(OTLPSpanExporter(endpoint="http://otel-collector:4318/v1/traces"))
)
trace.set_tracer_provider(trace_provider)
tracer = trace.get_tracer("kingservers.checkout")
# Metrics
metric_reader = PeriodicExportingMetricReader(
OTLPMetricExporter(endpoint="http://otel-collector:4318/v1/metrics")
)
meter_provider = MeterProvider(resource=resource, metric_readers=[metric_reader])
metrics.set_meter_provider(meter_provider)
meter = metrics.get_meter("kingservers.checkout")
request_counter = meter.create_counter(
"http.server.requests", unit="1", description="Total checkout requests"
)
# Logs
log_provider = LoggerProvider(resource=resource)
log_provider.add_log_record_processor(
BatchLogRecordProcessor(OTLPLogExporter(endpoint="http://otel-collector:4318/v1/logs"))
)
set_logger_provider(log_provider)
handler = LoggingHandler(level=logging.INFO, logger_provider=log_provider)
logging.basicConfig(level=logging.INFO, handlers=[handler])
logger = logging.getLogger("kingservers.checkout")
def handle_checkout():
with tracer.start_as_current_span("checkout") as span:
attrs = {"http.method": "POST", "http.route": "/checkout", "service.name": "checkout-api"}
request_counter.add(1, attrs)
logger.info("checkout started", extra={"order_id": "ORD-10001"})
time.sleep(0.12)
span.set_attribute("http.route", "/checkout")
span.set_attribute("checkout.payment_provider", "card")
logger.info("checkout finished", extra={"status_code": 200})
if __name__ == "__main__":
handle_checkout()
Если нужен совсем короткий путь для старта, можно показывать auto-instrumentation (opentelemetry-instrument). Но для production-практики обычно лучше сочетать auto-instrumentation для фреймворков и ручное обогащение spans/metrics/logs для бизнес-логики.

Как строить корреляцию между трассами, метриками и логами
Всю статью имеет смысл строить вокруг одной идеи: корреляция держится не на «магии Grafana», а на общем контексте.
OpenTelemetry определяет propagation как перенос context между сервисами и процессами; по умолчанию официальные propagators используют заголовки W3C Trace Context. Это позволяет trace_id пережить переходы через HTTP/gRPC и быть полезным для spans, logs и metrics.
Baggage — соседний механизм: key-value контекст рядом с trace context. Его важно использовать дозированно: он хорош для bounded-контекста (tenant/region/plan), но опасен для user-specific и high-cardinality данных, особенно если контекст уходит во внешние сервисы.
Самое прикладное правило для логов: trace_id должен быть queryable, но не индексируемым как label без необходимости. Для метрик логика обратная: общие service/resource атрибуты полезно продвигать в labels, а trace_id почти всегда ломает кардинальность — используйте exemplars.
Grafana здесь играет роль investigation UI: trace-to-logs, trace-to-metrics, derived fields и переходы из метрик по exemplars. Задача OpenTelemetry — обеспечить корректный общий контекст в данных, чтобы этот UX работал стабильно.
Схема triage-потока (практика)
flowchart LR
Alert["Алерт в Grafana/Prometheus\nlatency/error spike"] --> Metric["Метрика\np95 latency / error_rate"]
Metric --> Exemplar["Exemplar с trace_id"]
Exemplar --> Trace["Jaeger trace\nузкий запрос / ошибка"]
Trace --> Service["Проблемный span\nservice.name / operation"]
Service --> Logs["Loki logs\ntrace_id как field/metadata"]
Logs --> RootCause["Root cause:\nошибка кода / RPC timeout / DB проблема"]
Эта схема хорошо показывает, зачем нужны exemplars и почему correlation не сводится к «положить всё в один backend»: метрика ведёт к конкретному trace, trace — к проблемному span, а logs помогают добить root cause.
Best practices для production
Производительность и topology Если сервисов мало и они живут в одном контуре, Collector можно поставить рядом с приложением как agent или sidecar-like daemon. Но при росте нагрузки и количества сервисов лучше переходить к схеме agent + gateway: локальные collectors принимают данные рядом с workload, а центральный gateway выполняет routing, redaction, tail sampling и fan-out в backends. Официальная документация описывает gateway deployment как endpoint на cluster / datacenter / region. В каждом production pipeline полезно соблюдать два простых порядка: memory_limiter — первым, batch — после sampling/filtering. Первый защищает Collector от OOM и помогает backpressure, второй уменьшает overhead сети и число исходящих соединений. Документация Collector прямо рекомендует оба правила и даже советует координировать memory_limiter с GOMEMLIMIT для самого процесса Collector. Sampling Для distributed tracing sampling — не косметика, а контроль бюджета хранения и трафика. Если нужна диагностическая точность на ошибках и медленных запросах, tail sampling через Collector почти всегда практичнее head sampling в приложении: решение принимается после того, как собрана вся или почти вся трасса. Но есть критическое инфраструктурное условие: все spans одного trace должны попадать в один и тот же экземпляр Collector, иначе tail sampling работать корректно не будет. Рабочий production-паттерн обычно выглядит так: держать базовый probabilistic head sampling в сервисах только если traffic экстремально высок, а основные политики отбора делать на gateway-Collector через tail sampling по error status, latency и отдельным business-critical attributes. Это даёт predictable cost и сохраняет интересные traces. Cardinality, storage и стоимость Сигналы должны жить в «своих» storage model. Prometheus — это time series с labels; Loki — логовое хранилище, которое специально индексирует только metadata/labels и складывает сам лог в compressed chunks; Jaeger — trace backend, который принимает только trace data. Попытка переложить на один слой обязанности другого почти всегда заканчивается дорогими индексами, тяжелыми запросами и плохим UX. Отсюда несколько жёстких правил: не делать user_id, order_id, trace_id метками Prometheus; не делать trace_id или order_id labels в Loki; не продвигать в labels всё подряд из resource attributes; оставлять high-cardinality поля в logs/structured metadata, а сервисный контекст — в bounded labels. Безопасность OTLP-поток надо защищать точно так же, как любой другой internal telemetry ingress. Спецификация OTLP определяет transport configuration, а Collector docs explicitly показывают, что receivers и exporters можно защищать auth-механизмами через extensions; для headers и token-based auth есть стандартные OTLP env vars. На практике это означает TLS/mTLS на межсервисных границах, auth на публичных ingress’ах Collector и redaction чувствительных атрибутов до экспорта. Отдельный security-момент — propagation и baggage. Если вы переносите contextual data через headers, нужно считать это потенциально внешним входом. OpenTelemetry docs отдельно говорят о security best practices для external services и baggage: не нужно бездумно прокидывать в baggage персональные данные, access-level сведения и всё, что затем появится в логах, metric labels или traces.
Стадии внедрения, типичные ошибки и чеклист миграции
Что показывают реальные внедрения
Реальные внедрения почти никогда не делаются «большим взрывом». Миграция с vendor-specific observability на OpenTelemetry — пошаговый процесс: старт с dev/test, аккуратный пилот, auto-instrumentation, и отдельное внимание к культуре и договорённостям внутри команд.
В end-user обсуждениях часто всплывает одна и та же мысль: OpenTelemetry работает лучше, когда есть единые conventions и governance (в первую очередь service.name), а не когда каждая команда именует сервисы по-своему.
Кейсы вроде Farfetch полезны тем, что observability вынесена в центральную команду (tooling, deployments, traces/metrics/logs и обучение). Uplight показывает ценность OTel в гетерогенной среде (M&A, разные стеки): унифицировать vocabulary можно без принудительной унификации языков и фреймворков.
Типичные ошибки
Воспринимать OpenTelemetry как замену Prometheus/Loki/Jaeger. На деле OTel стандартизует instrumentation и transport, а backends остаются нужны.
Делать big-bang migration вместо поэтапного: сначала resource attributes + Collector + 1–3 эталонных сервиса, потом масштабировать покрытие.
Путать correlation и cardinality: trace_id должен помогать investigation, но не разрушать storage-модель Prometheus и Loki.
Не задавать service.name/service.version/deployment.environment.name с самого начала.
Бездумно использовать baggage и размножать PII по всем сигналам.
Collector стабилен под пикамиНеправильный порядок processors Security Включить TLS/auth, redaction, review baggage/resource attrs Телеметрия не утекает во внешние контуры без контроля Тащить PII в baggage и logs Масштабирование Перейти к agent+gateway и governance по conventions Новые сервисы подключаются без ручной экзотики Оставить каждую команду со своими схемами атрибутов В формулировке для блога это можно свести к одной сильной мысли: сначала стандартизируйте контекст и pipeline, потом наращивайте покрытие сигналов. Не наоборот.

Открытые вопросы и ограничения
В исходном ТЗ не указан язык примеров и не указана целевая инфраструктура, поэтому приведённые snippets специально сделаны нейтральными: они подходят для self-hosted сценария, демонстрационных Docker-стендов и как база для Kubernetes-адаптации. Если финальная статья должна быть жёстко привязана к Kubernetes, в следующий слой материала логично добавить k8sattributes, hostmetrics, filelog и agent/gateway deployment patterns отдельным приложением. Важно и то, что зрелость логового сигнала всё ещё неодинакова между языками: Go docs называют logs experimental, а Python docs говорят, что logs API & SDK находятся в development. Поэтому для продакшена в статье стоит рекомендовать version pinning SDK/exporters и обязательный staging soak-test перед rollout. Для русскоязычного блока «дополнительное чтение» можно уместно сослаться на свежий материал на Хабре про опыт внедрения OpenTelemetry и на русскоязычный пример Microsoft Learn по связке OpenTelemetry с Prometheus, Grafana и Jaeger. Но приоритетными источниками в статье всё равно должны оставаться официальные docs OpenTelemetry, спецификации OTLP и официальные docs Prometheus / Grafana / Loki / Jaeger, потому что именно они лучше всего отражают текущие интерфейсы и совместимость. https://opentelemetry.io/docs/