Instrumentação
Instrumentação é o ato de adicionar código de observabilidade a uma aplicação por conta própria.
Se você estiver instrumentando uma aplicação, será necessário utilizar o SDK do OpenTelemetry para sua linguagem. Você irá utilizar o SDK para inicializar o OpenTelemetry e a API para instrumentar seu código. Isso passará a emitir dados de telemetria da sua aplicação e de qualquer biblioteca que você tenha instalado que também possua instrumentação.
Se você estiver instrumentando uma biblioteca, instale apenas o pacote da API do OpenTelemetry para sua linguagem. Sua biblioteca não emitirá telemetria por conta própria; ela só emitirá telemetria quando fizer parte de uma aplicação que utiliza o SDK do OpenTelemetry. Para mais informações sobre a instrumentação de bibliotecas, consulte a seção Bibliotecas.
Para mais informações sobre a API e o SDK do OpenTelemetry, consulte a especificação.
Nesta página você irá aprender como adicionar rastros, métricas e logs ao código manualmente. No entanto, não é necessário utilizar apenas um tipo de instrumentação: utilize a instrumentação automática para começar e depois enriqueça o código com a instrumentação manual conforme necessário.
Além disso, para bibliotecas das quais o código depende, não é necessário escrever o código de instrumentação manualmente, pois elas podem vir com OpenTelemetry integrado de forma nativa ou é possível utilizar bibliotecas de instrumentação.
Preparação da aplicação de exemplo
Esta página utiliza uma versão modificada da aplicação de exemplo mostrada em Primeiros Passos para auxiliar no aprendizado sobre instrumentação manual.
Não é obrigatório utilizar a aplicação de exemplo: caso deseje instrumentar uma aplicação ou biblioteca própria, basta seguir as instruções desta seção para adaptar o processo ao seu código.
Dependências
Crie um arquivo package.json vazio do NPM em um novo diretório:
npm init -y
Em seguida, instale as dependências do Express.
npm install express @types/express
npm install -D tsx # ferramenta para executar arquivos TypeScript (.ts) diretamente com node
npm install express
Criar e iniciar um servidor HTTP
Para destacar a diferença entre instrumentar uma biblioteca e uma aplicação, separe a lógica de rolagem de dados em um arquivo de biblioteca, que será importado como dependência pelo arquivo da aplicação.
Crie o arquivo de biblioteca chamado dice.ts (ou dice.js caso não esteja
utilizando TypeScript) e adicione o seguinte código:
/*dice.ts*/
function rollOnce(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
export function rollTheDice(rolls: number, min: number, max: number) {
const result: number[] = [];
for (let i = 0; i < rolls; i++) {
result.push(rollOnce(min, max));
}
return result;
}
/*dice.js*/
function rollOnce(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function rollTheDice(rolls, min, max) {
const result = [];
for (let i = 0; i < rolls; i++) {
result.push(rollOnce(min, max));
}
return result;
}
module.exports = { rollTheDice };
Crie o arquivo da aplicação chamado app.ts (ou app.js caso não esteja
utilizando TypeScript) e adicione o seguinte código:
/*app.ts*/
import express, { type Express } from 'express';
import { rollTheDice } from './dice';
const PORT: number = parseInt(process.env.PORT || '8080');
const app: Express = express();
app.get('/rolldice', (req, res) => {
const rolls = req.query.rolls ? parseInt(req.query.rolls.toString()) : NaN;
if (isNaN(rolls)) {
res
.status(400)
.send("O parâmetro 'rolls' está ausente ou não é um número.");
return;
}
res.send(JSON.stringify(rollTheDice(rolls, 1, 6)));
});
app.listen(PORT, () => {
console.log(`Escutando requisições em http://localhost:${PORT}`);
});
/*app.js*/
const express = require('express');
const { rollTheDice } = require('./dice.js');
const PORT = parseInt(process.env.PORT || '8080');
const app = express();
app.get('/rolldice', (req, res) => {
const rolls = req.query.rolls ? parseInt(req.query.rolls.toString()) : NaN;
if (isNaN(rolls)) {
res
.status(400)
.send("O parâmetro 'rolls' está ausente ou não é um número.");
return;
}
res.send(JSON.stringify(rollTheDice(rolls, 1, 6)));
});
app.listen(PORT, () => {
console.log(`Escutando requisições em http://localhost:${PORT}`);
});
Para garantir que tudo está funcionando, execute a aplicação com o seguinte comando e abra http://localhost:8080/rolldice?rolls=12 no navegador.
$ npx tsx app.ts
Escutando requisições em http://localhost:8080
$ node app.js
Escutando requisições em http://localhost:8080
Configuração de instrumentação manual
Dependências
Instale os pacotes da API do OpenTelemetry:
npm install @opentelemetry/api @opentelemetry/resources @opentelemetry/semantic-conventions
Inicializar o SDK
Caso esteja instrumentando uma biblioteca, ignore esta etapa.
Para instrumentar uma aplicação Node.js, instale o SDK OpenTelemetry para Node.js:
npm install @opentelemetry/sdk-node
Antes que qualquer outro módulo da aplicação seja carregado, é necessário inicializar o SDK. Caso o SDK não seja inicializado, ou seja inicializado tarde demais, implementações no-op (pronunciada “no-op”, de “no operation”, significando “sem operação”) serão fornecidas a qualquer biblioteca que obtenha um Tracer ou Meter da API.
/*instrumentation.ts*/
import { NodeSDK } from '@opentelemetry/sdk-node';
import { ConsoleSpanExporter } from '@opentelemetry/sdk-trace-node';
import {
PeriodicExportingMetricReader,
ConsoleMetricExporter,
} from '@opentelemetry/sdk-metrics';
import { resourceFromAttributes } from '@opentelemetry/resources';
import {
ATTR_SERVICE_NAME,
ATTR_SERVICE_VERSION,
} from '@opentelemetry/semantic-conventions';
const sdk = new NodeSDK({
resource: resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'nomeDoServico',
[ATTR_SERVICE_VERSION]: '1.0',
}),
traceExporter: new ConsoleSpanExporter(),
metricReader: new PeriodicExportingMetricReader({
exporter: new ConsoleMetricExporter(),
}),
});
sdk.start();
/*instrumentation.mjs*/
import { NodeSDK } from '@opentelemetry/sdk-node';
import { ConsoleSpanExporter } from '@opentelemetry/sdk-trace-node';
import {
PeriodicExportingMetricReader,
ConsoleMetricExporter,
} from '@opentelemetry/sdk-metrics';
import { resourceFromAttributes } from '@opentelemetry/resources';
import {
ATTR_SERVICE_NAME,
ATTR_SERVICE_VERSION,
} from '@opentelemetry/semantic-conventions';
const sdk = new NodeSDK({
resource: resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'dice-server',
[ATTR_SERVICE_VERSION]: '0.1.0',
}),
traceExporter: new ConsoleSpanExporter(),
metricReader: new PeriodicExportingMetricReader({
exporter: new ConsoleMetricExporter(),
}),
});
sdk.start();
Para fins de depuração (debugging) e desenvolvimento local, o exemplo a seguir exporta telemetria para o console. Após concluir a configuração da instrumentação manual, será necessário configurar um exportador apropriado para exportar os dados de telemetria da aplicação para um ou mais backends de observabilidade.
O exemplo também define o atributo padrão obrigatório do SDK service.name, que
contém o nome lógico do serviço, e o atributo opcional (porém altamente
recomendado!) service.version, que contém a versão da API ou implementação do
serviço.
Existem métodos alternativos para definir atributos de recurso. Para mais informações, consulte Recursos.
Os exemplos a seguir que utilizam
--import instrumentation.ts (TypeScript) requerem Node.js v20 ou posterior. Caso
esteja utilizando Node.js v18, utilize o exemplo em JavaScript.
npx tsx --import ./instrumentation.ts app.ts
node --import ./instrumentation.mjs app.js
Esta configuração básica ainda não tem efeito na aplicação. É necessário adicionar código para rastros, métricas e/ou logs.
Também é possível registrar bibliotecas de instrumentação com o SDK OpenTelemetry para Node.js a fim de gerar dados de telemetria para as dependências. Para mais informações, veja Bibliotecas.
Rastros
Inicializar rastros
Caso esteja instrumentando uma biblioteca, ignore esta etapa.
Para habilitar rastros em uma aplicação, será
necessário ter um
TracerProvider inicializado,
que permitirá criar um Tracer.
Caso um TracerProvider não seja criado, as APIs do OpenTelemetry irão utilizar
uma implementação no-op e não irão gerar dados. Conforme explicado a seguir, o
arquivo instrumentation.ts (ou instrumentation.js) deve incluir todo o
código de inicialização do SDK.
Node.js
Caso as instruções para inicializar o SDK acima tenham
sido seguidas, já existe um TracerProvider configurado. É possível continuar
com obter um Tracer.
Navegador
A instrumentação do cliente para o navegador é experimental e, em grande parte, não está especificada. Caso possua interesse em auxiliar, entre em contato com o SIG de Instrumentação do Cliente.
Primeiro, certifique-se de ter instalado os pacotes corretos:
npm install @opentelemetry/sdk-trace-web
Em seguida, atualize o arquivo instrumentation.ts (ou instrumentation.js)
para conter todo o código de inicialização do SDK:
import {
defaultResource,
resourceFromAttributes,
} from '@opentelemetry/resources';
import {
ATTR_SERVICE_NAME,
ATTR_SERVICE_VERSION,
} from '@opentelemetry/semantic-conventions';
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
import {
BatchSpanProcessor,
ConsoleSpanExporter,
} from '@opentelemetry/sdk-trace-base';
const resource = defaultResource().merge(
resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'nome-do-servico',
[ATTR_SERVICE_VERSION]: '0.1.0',
}),
);
const exporter = new ConsoleSpanExporter();
const processor = new BatchSpanProcessor(exporter);
const provider = new WebTracerProvider({
resource: resource,
spanProcessors: [processor],
});
provider.register();
const opentelemetry = require('@opentelemetry/api');
const {
defaultResource,
resourceFromAttributes,
} = require('@opentelemetry/resources');
const {
ATTR_SERVICE_NAME,
ATTR_SERVICE_VERSION,
} = require('@opentelemetry/semantic-conventions');
const { WebTracerProvider } = require('@opentelemetry/sdk-trace-web');
const {
ConsoleSpanExporter,
BatchSpanProcessor,
} = require('@opentelemetry/sdk-trace-base');
const resource = defaultResource().merge(
resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'nome-do-servico',
[ATTR_SERVICE_VERSION]: '0.1.0',
}),
);
const exporter = new ConsoleSpanExporter();
const processor = new BatchSpanProcessor(exporter);
const provider = new WebTracerProvider({
resource: resource,
spanProcessors: [processor],
});
provider.register();
Será necessário gerar um pacote que inclua este arquivo na sua aplicação para que seja possível utilizar os rastros em todo o restante da aplicação web.
Isso ainda não terá efeito algum na sua aplicação: é preciso criar trechos para que a telemetria seja emitida.
Escolhendo o processador de trechos correto
Por padrão, o SDK do Node utiliza o BatchSpanProcessor, e este processador de
trechos também é escolhido no exemplo do SDK Web. O BatchSpanProcessor
processa trechos em lotes antes de serem exportados. Geralmente, este é o
processador correto a ser utilizado por uma aplicação.
Por outro lado, o SimpleSpanProcessor processa trechos conforme são criados.
Isso significa que se forem criados 5 trechos, cada um deles será processado e
exportado antes que o próximo seja criado no código. Esse comportamento pode ser
útil em cenários nos quais não se deseja correr o risco de perder um lote, ou ao
experimentar o OpenTelemetry em ambiente de desenvolvimento. No entanto, esta
escolha também pode gerar uma sobrecarga significativa, especialmente se os
trechos estiverem sendo exportados por uma rede — cada vez que um trecho é
criado, este trecho seria processado e enviado antes que a execução da aplicação
pudesse continuar.
Na maioria dos casos, utilize BatchSpanProcessor em vez de
SimpleSpanProcessor.
Obtendo um Tracer
Em qualquer parte da sua aplicação onde for escrito código de rastreamento
manual, deve-se chamar getTracer para obter um Tracer. Exemplo:
import opentelemetry from '@opentelemetry/api';
//...
const tracer = opentelemetry.trace.getTracer(
'nome-do-escopo-de-instrumentacao',
'versao-do-escopo-de-instrumentacao',
);
// Agora é possível usar 'tracer' para fazer rastreamento!
const opentelemetry = require('@opentelemetry/api');
//...
const tracer = opentelemetry.trace.getTracer(
'nome-do-escopo-de-instrumentacao',
'versao-do-escopo-de-instrumentacao',
);
// Agora é possível usar 'tracer' para fazer rastreamento!
Os valores nome-do-escopo-de-instrumentacao e
versao-do-escopo-de-instrumentacao devem identificar exclusivamente o
Escopo de Instrumentação, como o nome
de um pacote, módulo ou classe. O nome é obrigatório, enquanto a versão, embora
opcional, é recomendada.
De modo geral, recomenda-se chamar getTracer na aplicação sempre que for
necessário, em vez de exportar a instância de tracer para o restante do
código. Essa abordagem ajuda a evitar problemas mais complexos de carregamento
da aplicação quando há outras dependências envolvidas.
No caso da aplicação de exemplo, há dois pontos em que um Tracer pode ser obtido com o Escopo de Instrumentação apropriado:
Primeiro, no arquivo da aplicação app.ts (ou app.js):
/*app.ts*/
import { trace } from '@opentelemetry/api';
import express, { type Express } from 'express';
import { rollTheDice } from './dice';
const tracer = trace.getTracer('dice-server', '0.1.0');
const PORT: number = parseInt(process.env.PORT || '8080');
const app: Express = express();
app.get('/rolldice', (req, res) => {
const rolls = req.query.rolls ? parseInt(req.query.rolls.toString()) : NaN;
if (isNaN(rolls)) {
res
.status(400)
.send("O parâmetro 'rolls' está ausente ou não é um número.");
return;
}
res.send(JSON.stringify(rollTheDice(rolls, 1, 6)));
});
app.listen(PORT, () => {
console.log(`Escutando requisições em http://localhost:${PORT}`);
});
/*app.js*/
const { trace } = require('@opentelemetry/api');
const express = require('express');
const { rollTheDice } = require('./dice.js');
const tracer = trace.getTracer('dice-server', '0.1.0');
const PORT = parseInt(process.env.PORT || '8080');
const app = express();
app.get('/rolldice', (req, res) => {
const rolls = req.query.rolls ? parseInt(req.query.rolls.toString()) : NaN;
if (isNaN(rolls)) {
res
.status(400)
.send("O parâmetro 'rolls' está ausente ou não é um número.");
return;
}
res.send(JSON.stringify(rollTheDice(rolls, 1, 6)));
});
app.listen(PORT, () => {
console.log(`Escutando requisições em http://localhost:${PORT}`);
});
E segundo, no arquivo de biblioteca dice.ts (ou dice.js):
/*dice.ts*/
import { trace } from '@opentelemetry/api';
const tracer = trace.getTracer('dice-lib');
function rollOnce(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
export function rollTheDice(rolls: number, min: number, max: number) {
const result: number[] = [];
for (let i = 0; i < rolls; i++) {
result.push(rollOnce(min, max));
}
return result;
}
/*dice.js*/
const { trace } = require('@opentelemetry/api');
const tracer = trace.getTracer('dice-lib');
function rollOnce(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function rollTheDice(rolls, min, max) {
const result = [];
for (let i = 0; i < rolls; i++) {
result.push(rollOnce(min, max));
}
return result;
}
module.exports = { rollTheDice };
Criar trechos (spans)
Agora que os Tracers foram inicializados, é possível criar trechos.
A API do OpenTelemetry JavaScript oferece dois métodos para criar trechos:
tracer.startSpan: Inicia um novo trecho sem defini-lo no contexto.tracer.startActiveSpan: Inicia um novo trecho e executa a função de callback fornecida, passando o trecho criado como primeiro argumento. O novo trecho é definido no contexto ativo enquanto a função é executada.
Na maioria dos casos, é preferível utilizar tracer.startActiveSpan, pois ele
gerencia automaticamente o contexto ativo.
O código a seguir ilustra como criar um trecho ativo.
import { trace, type Span } from '@opentelemetry/api';
/* ... */
export function rollTheDice(rolls: number, min: number, max: number) {
// Cria um trecho ativo. O trecho deve ser finalizado.
return tracer.startActiveSpan('rollTheDice', (span: Span) => {
const result: number[] = [];
for (let i = 0; i < rolls; i++) {
result.push(rollOnce(min, max));
}
// Certifique-se de encerrar o trecho!
span.end();
return result;
});
}
function rollTheDice(rolls, min, max) {
// Cria um trecho ativo. O trecho deve ser finalizado.
return tracer.startActiveSpan('rollTheDice', (span) => {
const result = [];
for (let i = 0; i < rolls; i++) {
result.push(rollOnce(min, max));
}
// Certifique-se de encerrar o trecho!
span.end();
return result;
});
}
Caso as instruções utilizando a aplicação de exemplo tenham sido
seguidas até este ponto, é possível copiar o código acima no arquivo de
biblioteca dice.ts (ou dice.js). Você poderá visualizar trechos sendo
emitidos pela sua aplicação.
Execute o comando abaixo para iniciar a aplicação e, em seguida, envie
requisições acessando http://localhost:8080/rolldice?rolls=12 pelo navegador
ou utilizando o curl.
npx tsx --import ./instrumentation.ts app.ts
node --import ./instrumentation.mjs app.js
Após algum tempo, os trechos poderão ser vistos impressos no console pelo
ConsoleSpanExporter, similar a isto:
{
resource: {
attributes: {
'service.name': 'dice-server',
'service.version': '0.1.0',
// ...
}
},
instrumentationScope: { name: 'dice-lib', version: undefined, schemaUrl: undefined },
traceId: '30d32251088ba9d9bca67b09c43dace0',
parentSpanContext: undefined,
traceState: undefined,
name: 'rollTheDice',
id: 'cc8a67c2d4840402',
kind: 0,
timestamp: 1756165206470000,
duration: 35.584,
attributes: {},
status: { code: 0 },
events: [],
links: []
}
Criar trechos aninhados
Trechos aninhados permitem rastrear
operações que são aninhadas por natureza. Por exemplo, a função rollOnce()
abaixo representa uma operação aninhada. O exemplo a seguir cria um trecho
aninhado que rastreia rollOnce().
function rollOnce(i: number, min: number, max: number) {
return tracer.startActiveSpan(`rollOnce:${i}`, (span: Span) => {
const result = Math.floor(Math.random() * (max - min + 1) + min);
span.end();
return result;
});
}
export function rollTheDice(rolls: number, min: number, max: number) {
// Cria um trecho ativo. O trecho deve ser finalizado.
return tracer.startActiveSpan('rollTheDice', (parentSpan: Span) => {
const result: number[] = [];
for (let i = 0; i < rolls; i++) {
result.push(rollOnce(i, min, max));
}
// Certifique-se de encerrar o trecho!
parentSpan.end();
return result;
});
}
function rollOnce(i, min, max) {
return tracer.startActiveSpan(`rollOnce:${i}`, (span) => {
const result = Math.floor(Math.random() * (max - min + 1) + min);
span.end();
return result;
});
}
function rollTheDice(rolls, min, max) {
// Cria um trecho ativo. O trecho deve ser finalizado.
return tracer.startActiveSpan('rollTheDice', (parentSpan) => {
const result = [];
for (let i = 0; i < rolls; i++) {
result.push(rollOnce(i, min, max));
}
// Certifique-se de encerrar o trecho!
parentSpan.end();
return result;
});
}
Este código cria um trecho filho para cada rolagem, tendo o ID do parentSpan
como seu ID de pai (parent ID):
{
traceId: '6469e115dc1562dd768c999da0509615',
parentSpanContext: {
traceId: '6469e115dc1562dd768c999da0509615',
spanId: '38691692d6bc3395',
// ...
},
name: 'rollOnce:0',
id: '36423bc1ce7532b0',
timestamp: 1756165362215000,
duration: 85.667,
// ...
}
{
traceId: '6469e115dc1562dd768c999da0509615',
parentSpanContext: {
traceId: '6469e115dc1562dd768c999da0509615',
spanId: '38691692d6bc3395',
// ...
},
name: 'rollOnce:1',
id: 'ed9bbba2264d6872',
timestamp: 1756165362215000,
duration: 16.834,
// ...
}
{
traceId: '6469e115dc1562dd768c999da0509615',
parentSpanContext: undefined,
name: 'rollTheDice',
id: '38691692d6bc3395',
timestamp: 1756165362214000,
duration: 1022.209,
// ...
}
Criar trechos independentes
Os exemplos anteriores mostraram como criar um trecho ativo. Em alguns casos, você vai querer criar trechos inativos que são irmãos uns dos outros, em vez de serem aninhados.
const doWork = () => {
const span1 = tracer.startSpan('work-1');
// realizar alguma operação
const span2 = tracer.startSpan('work-2');
// realizar mais alguma operação
const span3 = tracer.startSpan('work-3');
// realizar ainda mais operações
span1.end();
span2.end();
span3.end();
};
Neste exemplo, span1, span2 e span3 são trechos irmãos e nenhum deles é
considerado o trecho ativo no momento. Eles compartilham o mesmo pai, em vez de
serem aninhados uns sob os outros.
Essa disposição pode ser útil caso você tenha unidades de trabalho que estão agrupadas, mas são conceitualmente independentes umas das outras.
Obter o trecho atual
Às vezes, é útil fazer algo com o trecho atual/ativo em um determinado ponto na execução da aplicação.
const activeSpan = opentelemetry.trace.getActiveSpan();
// fazer algo com o trecho ativo, opcionalmente encerrando-o se for apropriado para o caso de uso.
Obter um trecho do contexto
Também pode ser útil obter o trecho de um determinado contexto que não é necessariamente o trecho ativo.
const ctx = getContextFromSomewhere();
const span = opentelemetry.trace.getSpan(ctx);
// fazer algo com o trecho adquirido, opcionalmente encerrando-o se for apropriado para o caso de uso.
Atributos
Atributos permitem anexar pares de
chave/valor a um Trecho para que ele
carregue mais informações sobre a operação atual que está sendo rastreada.
function rollOnce(i: number, min: number, max: number) {
return tracer.startActiveSpan(`rollOnce:${i}`, (span: Span) => {
const result = Math.floor(Math.random() * (max - min + 1) + min);
// Adiciona um atributo ao trecho
span.setAttribute('dicelib.rolled', result.toString());
span.end();
return result;
});
}
function rollOnce(i, min, max) {
return tracer.startActiveSpan(`rollOnce:${i}`, (span) => {
const result = Math.floor(Math.random() * (max - min + 1) + min);
// Adiciona um atributo ao trecho
span.setAttribute('dicelib.rolled', result.toString());
span.end();
return result;
});
}
Também é possível adicionar atributos a um trecho quando ele é criado:
tracer.startActiveSpan(
'app.new-span',
{ attributes: { attribute1: 'value1' } },
(span) => {
// realizar alguma operação...
span.end();
},
);
function rollTheDice(rolls: number, min: number, max: number) {
return tracer.startActiveSpan(
'rollTheDice',
{ attributes: { 'dicelib.rolls': rolls.toString() } },
(span: Span) => {
/* ... */
},
);
}
function rollTheDice(rolls, min, max) {
return tracer.startActiveSpan(
'rollTheDice',
{ attributes: { 'dicelib.rolls': rolls.toString() } },
(span) => {
/* ... */
},
);
}
Atributos semânticos
Existem convenções semânticas para trechos que representam operações em protocolos bem conhecidos, como HTTP ou chamadas de banco de dados. As convenções semânticas para esses trechos estão definidas na especificação em Convenções Semânticas de Rastreamento (Trace semantic conventions). No exemplo deste guia, podem ser utilizados atributos de código fonte.
Primeiro, adicione o pacote de convenções semânticas como dependência da aplicação:
npm install --save @opentelemetry/semantic-conventions
Adicione as seguintes importações no topo do arquivo da aplicação:
import {
ATTR_CODE_FUNCTION_NAME,
ATTR_CODE_FILE_PATH,
} from '@opentelemetry/semantic-conventions';
const {
ATTR_CODE_FUNCTION_NAME,
ATTR_CODE_FILE_PATH,
} = require('@opentelemetry/semantic-conventions');
Por fim, atualize o código para incluir os atributos semânticos:
const doWork = () => {
tracer.startActiveSpan('app.doWork', (span) => {
span.setAttribute(ATTR_CODE_FUNCTION_NAME, 'doWork');
span.setAttribute(ATTR_CODE_FILE_PATH, __filename);
// Realizar alguma operação...
span.end();
});
};
Eventos de trecho
Um Evento de Trecho (Span Event)
é uma mensagem legível por humanos em um
Trecho que representa um evento
discreto, sem duração, que pode ser rastreado por um único carimbo de data e
hora (timestamp). Você pode pensar nisso como uma forma primitiva de
log.
span.addEvent('Executando algo');
const result = doWork();
Também é possível criar Eventos de Trecho com Atributos:
span.addEvent('some log', {
'log.severity': 'error',
'log.message': 'Data not found',
'request.id': requestId,
});
Links de trecho
Trechos podem ser criados com zero ou mais Links para outros Trechos que estão casualmente relacionados. Um cenário comum é correlacionar um ou mais rastros com o trecho atual.
const someFunction = (spanToLinkFrom) => {
const options = {
links: [
{
context: spanToLinkFrom.spanContext(),
},
],
};
tracer.startActiveSpan('app.someFunction', options, (span) => {
// Executa alguma operação...
span.end();
});
};
Estado de trecho
Um estado pode ser definido em um
Trecho, sendo tipicamente utilizado para
indicar que um Trecho não foi concluído com sucesso - Error. Por padrão, todos
os trechos possuem estado Unset, o que significa que o trecho foi concluído
sem erros. O estado Ok é reservado para quando você precisa definir
explicitamente um trecho como bem-sucedido, em vez de manter o padrão Unset
(ou seja, “sem erro”).
O estado pode ser definido a qualquer momento antes que o trecho seja finalizado.
import opentelemetry, { SpanStatusCode } from '@opentelemetry/api';
// ...
tracer.startActiveSpan('app.doWork', (span) => {
for (let i = 0; i <= Math.floor(Math.random() * 40000000); i += 1) {
if (i > 10000) {
span.setStatus({
code: SpanStatusCode.ERROR,
message: 'Erro',
});
}
}
span.end();
});
const opentelemetry = require('@opentelemetry/api');
// ...
tracer.startActiveSpan('app.doWork', (span) => {
for (let i = 0; i <= Math.floor(Math.random() * 40000000); i += 1) {
if (i > 10000) {
span.setStatus({
code: opentelemetry.SpanStatusCode.ERROR,
message: 'Erro',
});
}
}
span.end();
});
Registrar exceções
Registrar exceções no momento em que ocorrem é uma boa prática, especialmente quando combinado com a definição de estado do trecho.
import opentelemetry, { SpanStatusCode } from '@opentelemetry/api';
// ...
try {
doWork();
} catch (ex) {
if (ex instanceof Error) {
span.recordException(ex);
}
span.setStatus({ code: SpanStatusCode.ERROR });
}
const opentelemetry = require('@opentelemetry/api');
// ...
try {
doWork();
} catch (ex) {
if (ex instanceof Error) {
span.recordException(ex);
}
span.setStatus({ code: opentelemetry.SpanStatusCode.ERROR });
}
Usando sdk-trace-base e propagando manualmente o contexto do trecho
Em alguns casos, pode não ser possível utilizar o SDK do Node.js nem o SDK para Web. A maior diferença, além do código de inicialização, é que será necessário definir manualmente trechos como ativos no contexto atual para poder criar trechos aninhados.
Inicializando rastreamento com sdk-trace-base
A inicialização é semelhante a como seria feito com Node.js ou o SDK Web.
import opentelemetry from '@opentelemetry/api';
import {
CompositePropagator,
W3CTraceContextPropagator,
W3CBaggagePropagator,
} from '@opentelemetry/core';
import {
BasicTracerProvider,
BatchSpanProcessor,
ConsoleSpanExporter,
} from '@opentelemetry/sdk-trace-base';
opentelemetry.trace.setGlobalTracerProvider(
new BasicTracerProvider({
// Configura o Span Processor para enviar trechos ao exportador
spanProcessors: [new BatchSpanProcessor(new ConsoleSpanExporter())],
}),
);
opentelemetry.propagation.setGlobalPropagator(
new CompositePropagator({
propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()],
}),
);
// Isso é o que será acessado em todo o código de instrumentação
const tracer = opentelemetry.trace.getTracer('example-basic-tracer-node');
const opentelemetry = require('@opentelemetry/api');
const {
CompositePropagator,
W3CTraceContextPropagator,
W3CBaggagePropagator,
} = require('@opentelemetry/core');
const {
BasicTracerProvider,
ConsoleSpanExporter,
BatchSpanProcessor,
} = require('@opentelemetry/sdk-trace-base');
opentelemetry.trace.setGlobalTracerProvider(
new BasicTracerProvider({
// Configura o Span Processor para enviar trechos ao exportador
spanProcessors: [new BatchSpanProcessor(new ConsoleSpanExporter())],
}),
);
opentelemetry.propagation.setGlobalPropagator(
new CompositePropagator({
propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()],
}),
);
// Isso é o que será acessado em todo o código de instrumentação
const tracer = opentelemetry.trace.getTracer('example-basic-tracer-node');
Assim como os outros exemplos neste documento, isso exporta um Tracer que pode ser usado em toda a aplicação.
Criando trechos aninhados com sdk-trace-base
Para criar trechos aninhados, é necessário definir qualquer trecho atualmente
criado como o trecho ativo no contexto atual. O uso de startActiveSpan não é
recomendado aqui, pois ele não define o trecho como ativo.
const mainWork = () => {
const parentSpan = tracer.startSpan('main');
for (let i = 0; i < 3; i += 1) {
doWork(parentSpan, i);
}
// Certifique-se de encerrar o trecho pai!
parentSpan.end();
};
const doWork = (parent, i) => {
// Para criar um trecho filho, é necessário marcar o trecho atual (pai) como o trecho ativo
// no contexto, e então utilizar o contexto resultante para criar um trecho filho.
const ctx = opentelemetry.trace.setSpan(
opentelemetry.context.active(),
parent,
);
const span = tracer.startSpan(`doWork:${i}`, undefined, ctx);
// simular alguma operação aleatória.
for (let i = 0; i <= Math.floor(Math.random() * 40000000); i += 1) {
// vazio
}
// Certifique-se de encerrar o span filho! Se não o fizer,
// ele continuará a rastrear as operações além de 'doWork'!
span.end();
};
Todas as outras APIs se comportam da mesma forma ao usar o sdk-trace-base, em
comparação com os SDKs do Node.js ou Web.
Métricas
As métricas combinam medições individuais em agregados, gerando dados constantes independentemente da carga do sistema. Os agregados não contêm detalhes suficientes para diagnosticar problemas de baixo nível, mas complementam os trechos ao ajudar a identificar tendências e fornecer telemetria sobre o tempo de execução da aplicação.
Inicializar métricas
Caso esteja instrumentando uma biblioteca, ignore esta etapa.
Para ativar métricas em sua aplicação, é
necessário inicializar um
MeterProvider, que permitirá
criar um Meter.
Caso um MeterProvider não seja criado, as APIs do OpenTelemetry para métricas
utilizarão uma implementação no-op (pronunciada “no-op”, de “no operation”,
significando “sem operação”) e não conseguirão gerar dados. Conforme explicado a
seguir, modifique o arquivo instrumentation.ts (ou instrumentation.js) para
incluir todo o código de inicialização do SDK no Node e no navegador.
Node.js
Caso tenha seguido as instruções para inicializar o SDK acima, você já possui um MeterProvider configurado. É possível continuar com Obtendo um Meter.
Inicializando métricas com sdk-metrics
Em alguns casos, pode não ser possível — ou desejável — utilizar o SDK completo do OpenTelemetry para Node.js. O mesmo vale se você quiser utilizar o usar OpenTelemetry JavaScript no navegador.
Nesses casos, é possível inicializar as métricas com o pacote
@opentelemetry/sdk-metrics:
npm install @opentelemetry/sdk-metrics
Caso ainda não tenha feito isso, crie um arquivo instrumentation.ts (ou
instrumentation.js) que contenha todo o código de inicialização do SDK:
import opentelemetry from '@opentelemetry/api';
import {
ConsoleMetricExporter,
MeterProvider,
PeriodicExportingMetricReader,
} from '@opentelemetry/sdk-metrics';
import {
defaultResource,
resourceFromAttributes,
} from '@opentelemetry/resources';
import {
ATTR_SERVICE_NAME,
ATTR_SERVICE_VERSION,
} from '@opentelemetry/semantic-conventions';
const resource = defaultResource().merge(
resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'dice-server',
[ATTR_SERVICE_VERSION]: '0.1.0',
}),
);
const metricReader = new PeriodicExportingMetricReader({
exporter: new ConsoleMetricExporter(),
// O valor padrão é 60000ms (60 segundos). Vamos utilizar 10 segundos apenas para fins de demonstração.
exportIntervalMillis: 10000,
});
const myServiceMeterProvider = new MeterProvider({
resource: resource,
readers: [metricReader],
});
// Defina este MeterProvider para ser global para a aplicação que está sendo instrumentada.
opentelemetry.metrics.setGlobalMeterProvider(myServiceMeterProvider);
const opentelemetry = require('@opentelemetry/api');
const {
MeterProvider,
PeriodicExportingMetricReader,
ConsoleMetricExporter,
} = require('@opentelemetry/sdk-metrics');
const {
defaultResource,
resourceFromAttributes,
} = require('@opentelemetry/resources');
const {
ATTR_SERVICE_NAME,
ATTR_SERVICE_VERSION,
} = require('@opentelemetry/semantic-conventions');
const resource = defaultResource().merge(
resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'service-name-here',
[ATTR_SERVICE_VERSION]: '0.1.0',
}),
);
const metricReader = new PeriodicExportingMetricReader({
exporter: new ConsoleMetricExporter(),
// O valor padrão é 60000ms (60 segundos). Vamos utilizar 10 segundos apenas para fins de demonstração.
exportIntervalMillis: 10000,
});
const myServiceMeterProvider = new MeterProvider({
resource: resource,
readers: [metricReader],
});
// Defina este MeterProvider para ser global para a aplicação que está sendo instrumentada.
opentelemetry.metrics.setGlobalMeterProvider(myServiceMeterProvider);
Será necessário utilizar --import com este arquivo ao executar a aplicação,
como:
npx tsx --import ./instrumentation.ts app.ts
node --import ./instrumentation.mjs app.js
Agora que o MeterProvider está configurado, é possível adquirir um Meter.
Obtendo um Meter
Em qualquer parte da aplicação onde houver código instrumentado manualmente, é
possível chamar getMeter para obter um Meter. Por exemplo:
import opentelemetry from '@opentelemetry/api';
const myMeter = opentelemetry.metrics.getMeter(
'nome-do-escopo-de-instrumentacao',
'versao-do-escopo-de-instrumentacao',
);
// Agora é possível utilizar 'meter' para criar instrumentos!
const opentelemetry = require('@opentelemetry/api');
const myMeter = opentelemetry.metrics.getMeter(
'nome-do-escopo-de-instrumentacao',
'versao-do-escopo-de-instrumentacao',
);
// Agora é possível utilizar 'meter' para criar instrumentos!
Os valores nome-do-escopo-de-instrumentacao e
versao-do-escopo-de-instrumentacao devem identificar exclusivamente o
Escopo de Instrumentação, como o nome
de um pacote, módulo ou classe. O nome é obrigatório, enquanto a versão, embora
opcional, é recomendada.
De modo geral, recomenda-se chamar getMeter na aplicação sempre que for
necessário, em vez de exportar a instância de meter para o restante do código.
Essa abordagem ajuda a evitar problemas mais complexos de carregamento da
aplicação quando há outras dependências envolvidas.
No caso da aplicação de exemplo, há dois pontos em que um Meter pode ser obtido com o Escopo de Instrumentação apropriado:
Primeiro, no arquivo da aplicação app.ts (ou app.js):
/*app.ts*/
import { metrics, trace } from '@opentelemetry/api';
import express, { type Express } from 'express';
import { rollTheDice } from './dice';
const tracer = trace.getTracer('dice-server', '0.1.0');
const meter = metrics.getMeter('dice-server', '0.1.0');
const PORT: number = parseInt(process.env.PORT || '8080');
const app: Express = express();
app.get('/rolldice', (req, res) => {
const rolls = req.query.rolls ? parseInt(req.query.rolls.toString()) : NaN;
if (isNaN(rolls)) {
res
.status(400)
.send("O parâmetro 'rolls' está ausente ou não é um número.");
return;
}
res.send(JSON.stringify(rollTheDice(rolls, 1, 6)));
});
app.listen(PORT, () => {
console.log(`Escutando requisições em http://localhost:${PORT}`);
});
/*app.js*/
const { trace, metrics } = require('@opentelemetry/api');
const express = require('express');
const { rollTheDice } = require('./dice.js');
const tracer = trace.getTracer('dice-server', '0.1.0');
const meter = metrics.getMeter('dice-server', '0.1.0');
const PORT = parseInt(process.env.PORT || '8080');
const app = express();
app.get('/rolldice', (req, res) => {
const rolls = req.query.rolls ? parseInt(req.query.rolls.toString()) : NaN;
if (isNaN(rolls)) {
res
.status(400)
.send("O parâmetro 'rolls' está ausente ou não é um número.");
return;
}
res.send(JSON.stringify(rollTheDice(rolls, 1, 6)));
});
app.listen(PORT, () => {
console.log(`Escutando requisições em http://localhost:${PORT}`);
});
E segundo, no arquivo de biblioteca dice.ts (ou dice.js):
/*dice.ts*/
import { trace, metrics } from '@opentelemetry/api';
const tracer = trace.getTracer('dice-lib');
const meter = metrics.getMeter('dice-lib');
function rollOnce(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
export function rollTheDice(rolls: number, min: number, max: number) {
const result: number[] = [];
for (let i = 0; i < rolls; i++) {
result.push(rollOnce(min, max));
}
return result;
}
/*dice.js*/
const { trace, metrics } = require('@opentelemetry/api');
const tracer = trace.getTracer('dice-lib');
const meter = metrics.getMeter('dice-lib');
function rollOnce(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function rollTheDice(rolls, min, max) {
const result = [];
for (let i = 0; i < rolls; i++) {
result.push(rollOnce(min, max));
}
return result;
}
module.exports = { rollTheDice };
Agora que os Meters foram inicializados, é possível criar instrumentos de métrica.
Utilizando Contadores (Counters)
Contadores são usados para medir valores cumulativos, não-negativos e crescentes.
No caso da aplicação de exemplo, é possível contar quantas vezes os dados foram lançados:
/*dice.ts*/
const counter = meter.createCounter('dice-lib.rolls.counter');
function rollOnce(min: number, max: number) {
counter.add(1);
return Math.floor(Math.random() * (max - min + 1) + min);
}
/*dice.js*/
const counter = meter.createCounter('dice-lib.rolls.counter');
function rollOnce(min, max) {
counter.add(1);
return Math.floor(Math.random() * (max - min + 1) + min);
}
Utilizando Contadores UpDown (UpDown Counters)
Contadores UpDown podem incrementar e decrementar, permitindo observar um valor cumulativo que aumenta ou diminui ao longo do tempo.
const counter = myMeter.createUpDownCounter('events.counter');
//...
counter.add(1);
//...
counter.add(-1);
Utilizando Histogramas (Histograms)
Histogramas são utilizados para medir uma distribuição de valores ao longo do tempo.
Por exemplo, veja como reportar uma distribuição de tempos de resposta para uma rota de API com Express:
import express from 'express';
const app = express();
app.get('/', (_req, _res) => {
const histogram = myMeter.createHistogram('task.duration');
const startTime = new Date().getTime();
// realiza alguma operação em uma chamada de API
const endTime = new Date().getTime();
const executionTime = endTime - startTime;
// Registra a duração da operação da tarefa
histogram.record(executionTime);
});
const express = require('express');
const app = express();
app.get('/', (_req, _res) => {
const histogram = myMeter.createHistogram('task.duration');
const startTime = new Date().getTime();
// realiza alguma operação em uma chamada de API
const endTime = new Date().getTime();
const executionTime = endTime - startTime;
// Registra a duração da operação da tarefa
histogram.record(executionTime);
});
Utilizando Contadores Observáveis (Assíncronos)
Contadores observáveis (Observable counters) são utilizados para medir um valor aditivo, não-negativo e monotonicamente crescente.
const events = [];
const addEvent = (name) => {
events.push(name);
};
const counter = myMeter.createObservableCounter('events.counter');
counter.addCallback((result) => {
result.observe(events.length);
});
//... chamadas para addEvent
Utilizando Contadores UpDown Observáveis (Assíncronos)
Contadores UpDown observáveis (Observable UpDown counters) podem incrementar e decrementar, permitindo medir um valor cumulativo aditivo, não-negativo e não-monotonicamente crescente.
const events = [];
const addEvent = (name) => {
events.push(name);
};
const removeEvent = () => {
events.pop();
};
const counter = myMeter.createObservableUpDownCounter('events.counter');
counter.addCallback((result) => {
result.observe(events.length);
});
//... chamadas para addEvent e removeEvent
Utilizando Medidores Observáveis (Assíncronos)
Medidores Observáveis (Observable Gauges) devem ser usados para medir valores não-aditivos.
let temperature = 32;
const gauge = myMeter.createObservableGauge('temperature.gauge');
gauge.addCallback((result) => {
result.observe(temperature);
});
//... a variável temperature é modificada por um sensor
Descrevendo instrumentos
Ao criar instrumentos como contadores, histogramas, etc., é possível fornecer uma descrição.
const httpServerResponseDuration = myMeter.createHistogram(
'http.server.duration',
{
description: 'Distribuição do tempo de resposta do servidor HTTP',
unit: 'milliseconds',
valueType: ValueType.INT,
},
);
Em JavaScript, cada tipo de configuração significa o seguinte:
description- Uma descrição para o instrumento, legível por humanos.unit- A descrição da unidade de medida que o valor pretende representar. Por exemplo,millisecondspara medir duração, oubytespara contar número de bytes.valueType- O tipo de valor numérico usado nas medições.
De modo geral, recomenda-se descrever cada instrumento criado.
Adicionando Atributos
É possível adicionar atributos às métricas no momento em que são geradas:
const counter = myMeter.createCounter('my.counter');
counter.add(1, { 'algum.atributo.opcional': 'algum valor' });
Configurar Metric Views
Uma Metric View fornece aos desenvolvedores a capacidade de personalizar métricas expostas pelo SDK de Métricas.
Seletores
Para instanciar uma view, primeiro é necessário selecionar o instrumento de destino. Os seguintes seletores são válidos para métricas:
instrumentTypeinstrumentNamemeterNamemeterVersionmeterSchemaUrl
A seleção por instrumentName (do tipo string) oferece suporte a curingas
(wildcards), permitindo, por exemplo, selecionar todos os instrumentos com *
ou todos aqueles cujo nome começa com http, utilizando http*.
Exemplos
Filtrar atributos em todos os tipos de métrica:
const limitAttributesView = {
// exporta apenas o atributo 'environment'
attributeKeys: ['environment'],
// aplica a view a todos os instrumentos
instrumentName: '*',
};
Descartar todos os instrumentos cujo nome do Meter seja pubsub:
const dropView = {
aggregation: { type: AggregationType.DROP },
meterName: 'pubsub',
};
Definir intervalos de buckets explícitos para o Histograma chamado
http.server.duration:
const histogramView = {
aggregation: {
type: AggregationType.EXPLICIT_BUCKET_HISTOGRAM,
options: { boundaries: [0, 1, 5, 10, 15, 20, 25, 30] },
},
instrumentName: 'http.server.duration',
instrumentType: InstrumentType.HISTOGRAM,
};
Anexar ao Meter Provider
Depois de configuradas as views, é preciso anexá-las ao Meter Provider correspondente:
const meterProvider = new MeterProvider({
views: [limitAttributesView, dropView, histogramView],
});
Logs
As APIs e SDKs de logs estão atualmente em desenvolvimento.
Próximos passos
Também é necessário configurar um exportador apropriado para exportar seus dados de telemetria para um ou mais backends de observabilidade.
Feedback
Was this page helpful?
Thank you. Your feedback is appreciated!
Please let us know how we can improve this page. Your feedback is appreciated!