Observability is about the unknown-unknowns; the ability to understand internal system states just by observing its outputs outside.
Charity Majors, Observability Pionerr, Co-Founder of Honeycomb.io
Observability is about the unknown-unknowns; the ability to understand internal system states just by observing its outputs outside.
Charity Majors, Observability Pionerr, Co-Founder of Honeycomb.io
Observability про нефункциональные требования
другие аттрибуты качества — performance, availability, a11y
// Падает в 1% случаев
const checkout = async () => {
if (Math.random() > 0.01) return;
throw new Error("checkout error");
};
app.get("/data", async (req, res) => {
console.log("GET /data request received");
try {
await checkout();
res.status(200).json({ success: true });
} catch (error) {
console.error(error);
res.status(500).json({ success: false });
}
});
Error: checkout error
at checkout (/.../examples/1-simple-express/index.js:9:9)
at /.../examples/1-simple-express/index.js:16:11
at Layer.handleRequest (/.../examples/1-simple-express/node_modules/router/lib/layer.js:152:17)
at next (/.../examples/1-simple-express/node_modules/router/lib/route.js:157:13)
at Route.dispatch (/.../examples/1-simple-express/node_modules/router/lib/route.js:117:3)
at handle (/.../examples/1-simple-express/node_modules/router/index.js:435:11)
at Layer.handleRequest (/.../examples/1-simple-express/node_modules/router/lib/layer.js:152:17)
at /.../examples/1-simple-express/node_modules/router/index.js:295:15
at processParams (/.../examples/1-simple-express/node_modules/router/index.js:582:12)
at next (/.../examples/1-simple-express/node_modules/router/index.js:291:5)
// Падает в 1% случаев
const checkout = async () => {
if (Math.random() > 0.01) return;
throw new Error("checkout error");
};
app.get("/data", async (req, res) => {
console.log("GET /data request received");
try {
await checkout();
res.status(200).json({ success: true });
} catch (error) {
console.error(error);
res.status(500).json({ success: false });
}
});
const log = (msg) => {
console.log(`[${new Date().toISOString()}] ${msg}`);
};
//[2025-04-07T00:19:23.471Z] my log message
log("my log message");
const log = (msg) => {
console.log(`[${new Date().toISOString()}] ${msg}`);
};
//[2025-04-07T00:19:23.471Z] my log message
log("my log message");
Не наш выбор
const log = (msg, level) => {
console.log(`[${new Date().toISOString()}] ${level}: ${msg}`);
};
//[2025-04-07T00:19:23.471Z] Error: my log message
log("my log message" 'Error');
Не наш выбор
// logger.js
module.exports = require("pino")();
//index.js
log = require("./logger");
...
log.info('/data')
try {
await checkout();
res.status(200).json({ success: true });
} catch (error) {
log.error({ error }, 'MySupperError');
res.status(500).json({ success: false });
}
...
{"level":30,"time":1743987006941,"pid":31808,"hostname":"Alexs-MacBook-Pro.local","msg":"/data"}
{"level":30,"time":1743987006942,"pid":31808,"hostname":"Alexs-MacBook-Pro.local","msg":"success checkout"}
{"level":30,"time":1743987006945,"pid":31808,"hostname":"Alexs-MacBook-Pro.local","msg":"/data"}
{"level":30,"time":1743987006945,"pid":31808,"hostname":"Alexs-MacBook-Pro.local","msg":"success checkout"}
{"level":30,"time":1743987006946,"pid":31808,"hostname":"Alexs-MacBook-Pro.local","msg":"/data"}
{"level":30,"time":1743987006946,"pid":31808,"hostname":"Alexs-MacBook-Pro.local","msg":"success checkout"}
{"level":30,"time":1743987006946,"pid":31808,"hostname":"Alexs-MacBook-Pro.local","msg":"/data"}
{"level":30,"time":1743987006946,"pid":31808,"hostname":"Alexs-MacBook-Pro.local","msg":"success checkout"}
{"level":30,"time":1743987006946,"pid":31808,"hostname":"Alexs-MacBook-Pro.local","msg":"/data"}
{"level":30,"time":1743987006946,"pid":31808,"hostname":"Alexs-MacBook-Pro.local","msg":"success checkout"}
{"level":30,"time":1743987006947,"pid":31808,"hostname":"Alexs-MacBook-Pro.local","msg":"/data"}
{"level":30,"time":1743987006947,"pid":31808,"hostname":"Alexs-MacBook-Pro.local","msg":"success checkout"}
module.exports = require("pino")({
transport: {
target: "pino-pretty",
options: {
colorize: true,
},
},
});
module.exports = require("pino")({
transport: {
target: "pino-pretty",
options: {
colorize: true,
},
},
});
[04:07:27.975] INFO (53569): success checkout
[04:07:28.163] INFO (53569): /checkout
[04:07:28.164] INFO (53569): success checkout
[04:07:28.342] INFO (53569): /checkout
[04:07:28.343] ERROR (53569): error on checkout
err: {
"type": "Error",
"message": "checkout error",
"stack":
Error: checkout error
at checkout (.../examples/2-better-logging/index.js:14:9)
at .../examples/2-better-logging/index.js:21:11
at Layer.handleRequest (.../examples/2-better-logging/node_modules/router/lib/layer.js:152:17)
at next (.../examples/2-better-logging/node_modules/router/lib/route.js:157:13)
at Route.dispatch (.../examples/2-better-logging/node_modules/router/lib/route.js:117:3)
//ЭТО ОЧЕНЬ ПЛОХОЙ КОД, ОН НУЖЕН ДЛЯ ДЕМО. НЕ ПИШИТЕ ТАК
app.use((req, res, next) => {
const id = req.headers["id"] || "no-id";
req.logger = require("./logger.js").child({ "id": id });
next();
});
app.use((req, res, next) => {
const id = req.headers["id"] || "no-id";
req.axios = axios.create({headers: { id }});
next();
});
1. Заголовок traceparent
| Version | TraceId | ParentId | TraceFlags |
|---|---|---|---|
| 00 | 0af7651916cd43dd8448eb211c80319c | b7ad6b7169203331 | 01 |
2. Заголовок tracestate
key1=value1;key2=value2— Зачем мне тогда логи, ведь есть трейсы?
— Зачем мне тогда логи, ведь есть трейсы?
— Не все можно описать трейсами!
app.listen(port, () => {
logger.info(`application running on port ${port}`);
});
И все должно работать в связке
→ Вывод: Бесконечная рекурсия из-за ошибки в коде.
→ Вывод: Нужно добавить retry или увеличить лимиты.
→ Вывод: Проблема в запросе к медленной базе данных.
OpenTelemetry, also known as OTel, is a vendor-neutral open source Observability framework for instrumenting, generating, collecting, and exporting telemetry data such as traces, metrics, and logs.
OpenTelemetry, also known as OTel, is a vendor-neutral open source Observability framework for instrumenting, generating, collecting, and exporting telemetry data such as traces, metrics, and logs.
OpenTelemetry (OTel) разрабатывается как open-source проект при поддержке Cloud Native Computing Foundation (CNCF), которая является частью Linux Foundation
Можно пройти бесплатный треннинг (opentelemetry.io/training)
Основная идея
Развиваем
Получаем, преобрузуем, экспортируем
Выбор части данных для обработки/экспорта вместо всей информации
Объединение нескольких данных (например, спанов трейсов) в один пакет перед отправкой.
docker run \
-p 4317:4319 \
-p 4318:4320 \
-p 55679:55679 \
--name telemetry-collector \
otel/opentelemetry-collector-contrib:latest
//logs.js
oldConsole = global.console.log;
global.console.log = function (...args) {
oldConsole("====================");
oldConsole(...args);
oldConsole("====================");
};
NODE_OPTIONS="--require ./log.js" node hello.js
console.log('test')
#====================
#test
#====================
export OTEL_TRACES_EXPORTER="otlp"
export OTEL_EXPORTER_OTLP_ENDPOINT="your-endpoint"
export OTEL_NODE_RESOURCE_DETECTORS="env,host,os"
export OTEL_SERVICE_NAME="your-service-name"
export NODE_OPTIONS="--require @opentelemetry/auto-instrumentations-node/register"
node app.js
// tracing.js
const process = require('process');
const opentelemetry = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
const { Resource } = require('@opentelemetry/resources');
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');
// do not set headers in exporterOptions, the OTel spec recommends setting headers through ENV variables
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#specifying-headers-via-environment-variables
// highlight-start
const exporterOptions = {
url: 'https://ingest.<REGION>.signoz.cloud:443/v1/traces',
headers: {
"signoz-access-token": "<SIGNOZ_INGESTION_KEY>"
}
}
// highlight-end
const traceExporter = new OTLPTraceExporter(exporterOptions);
const sdk = new opentelemetry.NodeSDK({
traceExporter,
instrumentations: [getNodeAutoInstrumentations()],
resource: new Resource({
// highlight-next-line
[SemanticResourceAttributes.SERVICE_NAME]: '<SERVICE_NAME>'
})
});
// initialize the SDK and register with the OpenTelemetry API
// this enables the API to record telemetry
sdk.start()
// gracefully shut down the SDK on process exit
process.on('SIGTERM', () => {
sdk.shutdown()
.then(() => console.log('Tracing terminated'))
.catch((error) => console.log('Error terminating tracing', error))
.finally(() => process.exit(0));
});
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
const { SimpleSpanProcessor } = require('@opentelemetry/sdk-trace-base');
const { ConsoleSpanExporter } = require('@opentelemetry/sdk-trace-base');
const { trace } = require('@opentelemetry/api');
// Create a tracer provider
const tracerProvider = new NodeTracerProvider();
// Configure how spans are processed and exported
tracerProvider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));
// Register the provider
tracerProvider.register();
// Get a tracer
const tracer = trace.getTracer('example-tracer');
// Create a span
const span = tracer.startSpan('main-operation');
try {
// Business logic here
span.addEvent('Processing started');
// Nested span example
const childSpan = tracer.startSpan('database-query', {
parent: span
});
try {
// Simulate database work
childSpan.setAttribute('db.query', 'SELECT * FROM users');
// ... execute query
} finally {
childSpan.end();
}
span.addEvent('Processing completed');
} catch (error) {
span.recordException(error);
span.setStatus({ code: trace.SpanStatusCode.ERROR, message: error.message });
} finally {
span.end();
}
<!doctype html>
<html lang="en">
<head>
...
<base href="/" />
<meta
name="traceparent"
content="00-ab42124a3c573678d4d8b21ba52df3bf-d21f7bc17caa5aba-01"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>...</body>
</html>
Алексей Золотыx @zolotyh, t.me/zolotyh