برنامه نویسی

ردیابی توزیع شده با OpenTelemetry و Jaeger برای Nest Application

Summarize this content to 400 words in Persian Lang

مقدمه

آیا تا به حال برای شما پیش آمده است که اشکالی در تولید رخ داده باشد و هیچ ایده ای نداشته باشید که چه مشکلی رخ داده است، زیرا گزارش های شما دقیقاً به شما نمی گوید که چه چیزی اشتباه رخ داده است یا درخواستی که معمولاً پردازش آن طول می کشد.

گاهی اوقات اشکال زدایی این مسائل بدون سیستم ردیابی غیرممکن است. سیستم ردیابی مانند یک دوربین مداربسته است که همه چیز را ضبط می کند چه اتفاقی افتاد، چه زمانی اتفاق افتاد، ترتیب وقایع چگونه بود، هر رویداد چقدر طول کشید. این اطلاعات برای اشکال زدایی و شناسایی گلوگاه های عملکرد در برنامه های پیچیده توزیع شده حیاتی است.

پیش نیاز

NodeJS
تایپ اسکریپت
NestJS
داکر

اصطلاحات

ردیابی: یک ردیابی مانند یک نقشه سفر کامل از یک درخواست است که در کل سیستم توزیع شده شما حرکت می کند. آن را به عنوان یک گزارش سفر با جزئیات تصور کنید که درخواستی را از نقطه شروع تا مقصد نهایی خود دنبال می کند و هر توقف و تعامل در طول مسیر را به تصویر می کشد.

ابزار دقیق: فرآیند افزودن کد به برنامه شما برای جمع آوری داده های تله متری. مانند نصب ردیاب های GPS در قسمت های مختلف سیستم شماست.
صادر کننده: جزء مسئول ارسال داده های ردیابی جمع آوری شده به یک سیستم Back-end برای ذخیره سازی و تجزیه و تحلیل. به آن به عنوان یک سرویس پستی فکر کنید که گزارش سفر شما را به یک بایگانی مرکزی ارسال می کند.

دهانه:

Root span: اولین دهانه در یک ردیابی که شروع کل سفر درخواست را نشان می دهد. مثل نقطه شروع سفرنامه شماست.
Child span: دهانه ای که در یک دهانه دیگر تودرتو شده است، که نشان دهنده یک عملیات خاص تر در یک فرآیند گسترده تر است.

انتشار متن: مکانیسم انتقال اطلاعات ردیابی بین خدمات و اجزای مختلف. مانند پاسپورت یک مسافر است که حاوی جزئیات کامل سفر است.

معیارها: متریک ها داده های عددی هستند که عملکرد، سلامت و رفتار برنامه را نشان می دهند.

سیاههها: گزارش‌ها ورودی‌های متنی هستند که الگوهای استفاده، فعالیت‌ها و عملیات را در برنامه شما توصیف می‌کنند.

سه سوار قابل مشاهده

مشاهده‌پذیری به شما امکان می‌دهد یک سیستم را از بیرون درک کنید و به شما اجازه می‌دهد در مورد آن سیستم بدون اطلاع از عملکرد درونی آن سؤال بپرسید. این به شما امکان می دهد تا به راحتی مشکلات جدید را عیب یابی و مدیریت کنید، یعنی “ناشناخته های ناشناخته”. همچنین به سوال پاسخ می دهد “چرا این اتفاق می افتد؟”

راه اندازی پروژه

pnpm i -g @nestjs/cli
nest new tracing-app
cd tracing-app

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نصب وابستگی ها

کتابخانه های مرتبط Jaeger و OpenTelemetry را نصب کنید:

pnpm install @opentelemetry/sdk-trace-node @opentelemetry/resources @opentelemetry/sdk-trace-base
pnpm install @opentelemetry/instrumentation @prisma/instrumentation @opentelemetry/instrumentation-net @opentelemetry/instrumentation-http @opentelemetry/instrumentation-express
pnpm install @opentelemetry/exporter-trace-otlp-http
pnpm install @opentelemetry/api @opentelemetry/semantic-conventions

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

Prisma ORM و SQLite را نصب کنید:

pnpm install @prisma/client sqlite3 class-validator
pnpm install prisma –save-dev
pnpm install –save @nestjs/swagger

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

Prisma را راه اندازی کنید:

npx prisma init

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این یک ایجاد خواهد کرد prisma دایرکتوری با a schema.prisma فایل

datasource db {
provider = “sqlite”
url = “file:./dev.db”
}

generator client {
provider = “prisma-client-js”
}

model User {
id Int @id @default(autoincrement())
name String
email String @unique
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

مهاجرت های Prisma را اجرا کنید:

npx prisma migrate dev –name init

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

کلاینت Prisma را ایجاد کنید:

npx prisma generate

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

یک نقطه پایانی CRUD را تنظیم کنید

یک ماژول CRUD برای کاربران ایجاد کنید

pnpm nest generate resource users

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این یک ایجاد خواهد کرد users ماژول با کنترلر، سرویس و DTO.

ایجاد یک prisma.service.ts فایل در پوشه پریسما

import { Injectable, OnModuleInit, OnModuleDestroy } from ‘@nestjs/common’;
import { PrismaClient } from ‘@prisma/client’;

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
async onModuleInit() {
await this.$connect();
}

async onModuleDestroy() {
await this.$disconnect();
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

را به روز کنید users.module.ts فایل برای گنجاندن PrismaService:

import { Module } from ‘@nestjs/common’;
import { UsersService } from ‘./users.service’;
import { UsersController } from ‘./users.controller’;
import { PrismaService } from ‘../../prisma/prisma.service’;

@Module({
controllers: [UsersController],
providers: [UsersService, PrismaService],
})
export class UsersModule { }

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

یک فایل به نام ایجاد کنید create-user.dto.ts در users/dto دایرکتوری:

import { IsEmail, IsNotEmpty, IsString } from ‘class-validator’;
import { ApiProperty } from ‘@nestjs/swagger’;

export class CreateUserDto {
@ApiProperty({
description: ‘The name of the user’,
example: ‘John Doe’,
})
@IsNotEmpty()
@IsString()
name: string;

@ApiProperty({
description: ‘The email of the user’,
example: ’email@domain.com’,
})
@IsNotEmpty()
@IsEmail()
email: string;
}

export class UpdateUserDto extends PartialType(CreateUserDto) {}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

را به روز کنید users.service.ts فایل برای استفاده از Prisma:

import { Injectable } from ‘@nestjs/common’;
import { PrismaService } from ‘../prisma/prisma.service’;
import { CreateUserDto } from ‘./dto/create-user.dto’;
import { UpdateUserDto } from ‘./dto/update-user.dto’;

@Injectable()
export class UsersService {
constructor(private prisma: PrismaService) {}

create(createUserDto: CreateUserDto) {
return this.prisma.user.create({
data: createUserDto,
});
}

findAll() {
return this.prisma.user.findMany();
}

findOne(id: number) {
return this.prisma.user.findUnique({
where: { id },
});
}

update(id: number, updateUserDto: UpdateUserDto) {
return this.prisma.user.update({
where: { id },
data: updateUserDto,
});
}

remove(id: number) {
return this.prisma.user.delete({
where: { id },
});
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

را به روز کنید users.controller.ts فایل:

import { Controller, Get, Post, Body, Patch, Param, Delete } from ‘@nestjs/common’;
import { UsersService } from ‘./users.service’;
import { CreateUserDto } from ‘./dto/create-user.dto’;
import { UpdateUserDto } from ‘./dto/update-user.dto’;
import { ApiGoneResponse, ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from ‘@nestjs/swagger’;

@ApiTags(‘users’)
@Controller(‘users’)
export class UsersController {
constructor(private readonly usersService: UsersService) { }

@ApiOperation({ summary: ‘Create user’ })
@ApiOkResponse({ description: ‘User created’ })
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}

@ApiOperation({ summary: ‘Get all users’ })
@ApiOkResponse({ description: ‘Users found’ })
@Get()
findAll() {
return this.usersService.findAll();
}

@ApiOperation({ summary: ‘Get user by id’ })
@ApiOkResponse({ description: ‘User found’ })
@ApiNotFoundResponse({ description: ‘User not found’ })
@ApiParam({ name: ‘id’, description: ‘User id’ })
@Get(‘:id’)
findOne(@Param(‘id’) id: string) {
return this.usersService.findOne(+id);
}

@ApiOperation({ summary: ‘Update user’ })
@ApiOkResponse({ description: ‘User updated’ })
@ApiNotFoundResponse({ description: ‘User not found’ })
@ApiParam({ name: ‘id’, description: ‘User id’ })
@Patch(‘:id’)
update(@Param(‘id’) id: string, @Body() updateUserDto: UpdateUserDto) {
return this.usersService.update(+id, updateUserDto);
}

@ApiOperation({ summary: ‘Delete user’ })
@ApiGoneResponse({ description: ‘User deleted’ })
@ApiParam({ name: ‘id’, description: ‘User id’ })
@Delete(‘:id’)
remove(@Param(‘id’) id: string) {
return this.usersService.remove(+id);
}
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

پیکربندی صادرکنندگان

یک فایل ایجاد کنید ردیابی.ts در شما src دایرکتوری:

import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from ‘@opentelemetry/semantic-conventions’;
import { BatchSpanProcessor } from ‘@opentelemetry/sdk-trace-base’;
import { ExpressInstrumentation } from ‘@opentelemetry/instrumentation-express’;
import { HttpInstrumentation } from ‘@opentelemetry/instrumentation-http’;
import { NetInstrumentation } from ‘@opentelemetry/instrumentation-net’;
import { NodeTracerProvider } from ‘@opentelemetry/sdk-trace-node’;
import { OTLPTraceExporter } from ‘@opentelemetry/exporter-trace-otlp-http’;
import { PrismaInstrumentation } from ‘@prisma/instrumentation’;
import { Resource } from ‘@opentelemetry/resources’;
import { diag, DiagConsoleLogger, DiagLogLevel } from ‘@opentelemetry/api’;
import { registerInstrumentations } from ‘@opentelemetry/instrumentation’;

export function setupTracing() {
// Enable OpenTelemetry diagnostic logging
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO);

// Create a resource with service information
const resource = new Resource({
[ATTR_SERVICE_NAME]: process.env.SERVICE_NAME || ‘tracer-app’,
[ATTR_SERVICE_VERSION]: process.env.npm_package_version || ‘1.0.0’,
});

const otlpExporter = new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || ‘http://localhost:4318/v1/traces’,
});

// Create tracer provider with resource and span processors
const provider = new NodeTracerProvider({
resource,
spanProcessors: [
new BatchSpanProcessor(otlpExporter, {
maxQueueSize: 100,
scheduledDelayMillis: 5000,
exportTimeoutMillis: 30000,
maxExportBatchSize: 50,
})
] });

// Register instrumentations with more comprehensive coverage
registerInstrumentations({
tracerProvider: provider,
instrumentations: [
new HttpInstrumentation({
requestHook: (span, request) => {
span.setAttribute(‘http.request.method’, request.method);
},
}),
new NetInstrumentation(),
new ExpressInstrumentation(),
new PrismaInstrumentation({ middleware: true }),
],
});

// Register the provider
provider.register();

// Return the provider for potential manual instrumentation
return provider;
}

// Call this at application startup
setupTracing();

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

توضیح

ثبت تشخیصی: ورود به سیستم تشخیصی را با استفاده از ثبت‌کننده کنسول در قسمت فعال می‌کند INFO سطح برای رفع اشکال تنظیم ردیابی.

اولیه سازی منابع:

متادیتا را درباره سرویس تعریف می کند، مانند SERVICE_NAME و SERVICE_VERSION.
این ابرداده به هر ردیابی متصل می شود و به شناسایی خدماتی که ردیابی متعلق است کمک می کند.

صادرکننده ردیابی OTLP: پروتکل OpenTelemetry را پیکربندی می کند (OTLP) صادر کننده برای ارسال داده های ردیابی به باطن که در حال حاضر Jaeger از آن استفاده می کند HTTP پروتکل توجه داشته باشید که Jaeger را می توان با باطن دیگری مانند Honeycomb، Zipkin و غیره تعویض کرد و شما پروتکل را به کارآمدتر تغییر دهید. GRPC از جاری HTTP.

ارائه دهنده Tracer با پردازنده Span: a را ایجاد می کند NodeTracerProvider، که ردیاب ها و گستره های زیر را مدیریت می کند:

ثبت ابزار دقیق: به طور خودکار ردیابی کتابخانه ها و چارچوب ها را می گیرد:

HttpInstrumentation: زمان صرف شده توسط درخواست ها/پاسخ های HTTP را ثبت می کند.

NetInstrumentation: زمان صرف شده توسط رویدادهای شبکه سطح پایین را ثبت می کند.

ExpressInstrumentation: زمان مصرف شده توسط میان افزار Express و مسیرها را ردیابی می کند.

PrismaInstrumentation: زمان صرف شده توسط پرسش های SQL تولید شده توسط Prisma را ردیابی می کند

کد ابزار دقیق را در برنامه خود وارد کنید

پیکربندی ردیابی را در فایل برنامه اصلی خود وارد کرده و مقداردهی اولیه کنید main.ts:

import { NestFactory } from ‘@nestjs/core’;
import { SwaggerModule, DocumentBuilder } from ‘@nestjs/swagger’;
import { AppModule } from ‘./app.module’;
import ‘./tracing’;

async function bootstrap() {
const app = await NestFactory.create(AppModule);

const config = new DocumentBuilder()
.setTitle(‘Tracing example’)
.setDescription(‘The tracing API description’)
.setVersion(‘1.0’)
.addTag(‘tracing’)
.build();
const documentFactory = () => SwaggerModule.createDocument(app, config);
SwaggerModule.setup(‘api-docs’, app, documentFactory);

await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

برنامه خود را اجرا کنید

pnpm run start:dev

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

تنظیم Jaeger برای محیط توسعه

ساده ترین راه برای راه اندازی Jaeger این است که وینچ docker-compose در محیط توسعه به خوبی کار می کند.

ایجاد کنید docker-compose.yaml فایل:

services:
jaeger:
image: jaegertracing/all-in-one:1.63.0
container_name: jaeger
environment:
COLLECTOR_OTLP_ENABLED: “true”
ports:
– “4317:4317” # For Jaeger-GRPC
– “4318:4318” # For Jaeger-HTTP
– “16686:16686” # # Web UI

networks:
default:
driver: bridge

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

برنامه Containerize (اختیاری)

می توانید استفاده کنید docker init اگر نسخه جدیدتر Docker را نصب کرده باشید، دستور تولید خودکار یک Dockerfile بهینه شده را صادر کنید.

# Arguments for versions
ARG NODE_VERSION=20.18.0
ARG PNPM_VERSION=9.12.2
ARG ALPINE_VERSION=3.20

################################################################################
# Base stage: Build the application
FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION} AS builder

# Set working directory
WORKDIR /usr/src/app

# Install pnpm globally with cache
RUN –mount=type=cache,target=/root/.npm \
npm install -g pnpm@${PNPM_VERSION}

# Copy package.json and pnpm-lock.yaml to install dependencies
COPY ../package.json pnpm-lock.yaml ./

# Install dependencies with cache
RUN –mount=type=cache,target=/root/.pnpm-store \
pnpm install –frozen-lockfile

# Copy the all application code
COPY .. .

# Setup prisma
RUN pnpm prisma generate

# Build the application
RUN pnpm run build

# Runner Stage
FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION} AS runner

# Set working directory
WORKDIR /usr/src/app

# Copy the built application from the builder stage
COPY –from=builder /usr/src/app/dist ./dist
COPY ../package.json pnpm-lock.yaml ./
COPY ../prisma/schema.prisma ./prisma/schema.prisma

# Install pnpm globally
RUN –mount=type=cache,target=/root/.npm \
npm install -g pnpm@${PNPM_VERSION}

# Install dependencies with cache
RUN –mount=type=cache,target=/root/.pnpm-store \
pnpm install –frozen-lockfile –prod

# Set NODE_ENV to production
ENV NODE_ENV=production

# Run the application
CMD [“pnpm”, “run”, “start:prod”]

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

Swagger UI

از http://localhost:3000/api-docs دیدن کنید و چند تماس API برقرار کنید

تجسم آثار

مرورگر خود را باز کنید و به http://localhost:16686 برای دیدن رابط کاربری Jaeger. چند درخواست را اجرا کنید و کلیک کنید ردیابی را پیدا کنید و روی یک Trace کلیک کنید

مقدمه

آیا تا به حال برای شما پیش آمده است که اشکالی در تولید رخ داده باشد و هیچ ایده ای نداشته باشید که چه مشکلی رخ داده است، زیرا گزارش های شما دقیقاً به شما نمی گوید که چه چیزی اشتباه رخ داده است یا درخواستی که معمولاً پردازش آن طول می کشد.

گاهی اوقات اشکال زدایی این مسائل بدون سیستم ردیابی غیرممکن است. سیستم ردیابی مانند یک دوربین مداربسته است که همه چیز را ضبط می کند چه اتفاقی افتاد، چه زمانی اتفاق افتاد، ترتیب وقایع چگونه بود، هر رویداد چقدر طول کشید. این اطلاعات برای اشکال زدایی و شناسایی گلوگاه های عملکرد در برنامه های پیچیده توزیع شده حیاتی است.

پیش نیاز

  • NodeJS

  • تایپ اسکریپت

  • NestJS

  • داکر

اصطلاحات

  • ردیابی: یک ردیابی مانند یک نقشه سفر کامل از یک درخواست است که در کل سیستم توزیع شده شما حرکت می کند. آن را به عنوان یک گزارش سفر با جزئیات تصور کنید که درخواستی را از نقطه شروع تا مقصد نهایی خود دنبال می کند و هر توقف و تعامل در طول مسیر را به تصویر می کشد.

توضیحات تصویر

  • ابزار دقیق: فرآیند افزودن کد به برنامه شما برای جمع آوری داده های تله متری. مانند نصب ردیاب های GPS در قسمت های مختلف سیستم شماست.

  • صادر کننده: جزء مسئول ارسال داده های ردیابی جمع آوری شده به یک سیستم Back-end برای ذخیره سازی و تجزیه و تحلیل. به آن به عنوان یک سرویس پستی فکر کنید که گزارش سفر شما را به یک بایگانی مرکزی ارسال می کند.

  • دهانه:

    • Root span: اولین دهانه در یک ردیابی که شروع کل سفر درخواست را نشان می دهد. مثل نقطه شروع سفرنامه شماست.
    • Child span: دهانه ای که در یک دهانه دیگر تودرتو شده است، که نشان دهنده یک عملیات خاص تر در یک فرآیند گسترده تر است.
  • انتشار متن: مکانیسم انتقال اطلاعات ردیابی بین خدمات و اجزای مختلف. مانند پاسپورت یک مسافر است که حاوی جزئیات کامل سفر است.

  • معیارها: متریک ها داده های عددی هستند که عملکرد، سلامت و رفتار برنامه را نشان می دهند.

  • سیاههها: گزارش‌ها ورودی‌های متنی هستند که الگوهای استفاده، فعالیت‌ها و عملیات را در برنامه شما توصیف می‌کنند.

سه سوار قابل مشاهده

مشاهده‌پذیری به شما امکان می‌دهد یک سیستم را از بیرون درک کنید و به شما اجازه می‌دهد در مورد آن سیستم بدون اطلاع از عملکرد درونی آن سؤال بپرسید. این به شما امکان می دهد تا به راحتی مشکلات جدید را عیب یابی و مدیریت کنید، یعنی “ناشناخته های ناشناخته”. همچنین به سوال پاسخ می دهد “چرا این اتفاق می افتد؟”

توضیحات تصویر

راه اندازی پروژه

pnpm i -g @nestjs/cli
nest new tracing-app
cd tracing-app
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نصب وابستگی ها

کتابخانه های مرتبط Jaeger و OpenTelemetry را نصب کنید:

pnpm install @opentelemetry/sdk-trace-node @opentelemetry/resources @opentelemetry/sdk-trace-base 
pnpm install @opentelemetry/instrumentation @prisma/instrumentation @opentelemetry/instrumentation-net @opentelemetry/instrumentation-http @opentelemetry/instrumentation-express
pnpm install @opentelemetry/exporter-trace-otlp-http
pnpm install @opentelemetry/api @opentelemetry/semantic-conventions
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

Prisma ORM و SQLite را نصب کنید:

pnpm install @prisma/client sqlite3 class-validator
pnpm install prisma --save-dev
pnpm install --save @nestjs/swagger
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

Prisma را راه اندازی کنید:

npx prisma init
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این یک ایجاد خواهد کرد prisma دایرکتوری با a schema.prisma فایل

datasource db {
  provider = "sqlite"
  url      = "file:./dev.db"
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id    Int     @id @default(autoincrement())
  name  String
  email String  @unique
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

مهاجرت های Prisma را اجرا کنید:

npx prisma migrate dev --name init
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

کلاینت Prisma را ایجاد کنید:

npx prisma generate
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

یک نقطه پایانی CRUD را تنظیم کنید

یک ماژول CRUD برای کاربران ایجاد کنید

pnpm nest generate resource users
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این یک ایجاد خواهد کرد users ماژول با کنترلر، سرویس و DTO.

ایجاد یک prisma.service.ts فایل در پوشه پریسما

import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  async onModuleInit() {
    await this.$connect();
  }

  async onModuleDestroy() {
    await this.$disconnect();
  }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

را به روز کنید users.module.ts فایل برای گنجاندن PrismaService:

import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { PrismaService } from '../../prisma/prisma.service';

@Module({
  controllers: [UsersController],
  providers: [UsersService, PrismaService],
})
export class UsersModule { }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

یک فایل به نام ایجاد کنید create-user.dto.ts در users/dto دایرکتوری:

import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';

export class CreateUserDto {
    @ApiProperty({
        description: 'The name of the user',
        example: 'John Doe',
    })
    @IsNotEmpty()
    @IsString()
    name: string;

    @ApiProperty({
        description: 'The email of the user',
        example: 'email@domain.com',
    })
    @IsNotEmpty()
    @IsEmail()
    email: string;
}

export class UpdateUserDto extends PartialType(CreateUserDto) {}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

را به روز کنید users.service.ts فایل برای استفاده از Prisma:

import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

@Injectable()
export class UsersService {
  constructor(private prisma: PrismaService) {}

  create(createUserDto: CreateUserDto) {
    return this.prisma.user.create({
      data: createUserDto,
    });
  }

  findAll() {
    return this.prisma.user.findMany();
  }

  findOne(id: number) {
    return this.prisma.user.findUnique({
      where: { id },
    });
  }

  update(id: number, updateUserDto: UpdateUserDto) {
    return this.prisma.user.update({
      where: { id },
      data: updateUserDto,
    });
  }

  remove(id: number) {
    return this.prisma.user.delete({
      where: { id },
    });
  }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

را به روز کنید users.controller.ts فایل:

import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { ApiGoneResponse, ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger';

@ApiTags('users')
@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) { }

  @ApiOperation({ summary: 'Create user' })
  @ApiOkResponse({ description: 'User created' })
  @Post()
  create(@Body() createUserDto: CreateUserDto) {
    return this.usersService.create(createUserDto);
  }

  @ApiOperation({ summary: 'Get all users' })
  @ApiOkResponse({ description: 'Users found' })
  @Get()
  findAll() {
    return this.usersService.findAll();
  }

  @ApiOperation({ summary: 'Get user by id' })
  @ApiOkResponse({ description: 'User found' })
  @ApiNotFoundResponse({ description: 'User not found' })
  @ApiParam({ name: 'id', description: 'User id' })
  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.usersService.findOne(+id);
  }

  @ApiOperation({ summary: 'Update user' })
  @ApiOkResponse({ description: 'User updated' })
  @ApiNotFoundResponse({ description: 'User not found' })
  @ApiParam({ name: 'id', description: 'User id' })
  @Patch(':id')
  update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
    return this.usersService.update(+id, updateUserDto);
  }

  @ApiOperation({ summary: 'Delete user' })
  @ApiGoneResponse({ description: 'User deleted' })
  @ApiParam({ name: 'id', description: 'User id' })
  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.usersService.remove(+id);
  }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

پیکربندی صادرکنندگان

یک فایل ایجاد کنید ردیابی.ts در شما src دایرکتوری:

import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
import { NetInstrumentation } from '@opentelemetry/instrumentation-net';
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { PrismaInstrumentation } from '@prisma/instrumentation';
import { Resource } from '@opentelemetry/resources';
import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';
import { registerInstrumentations } from '@opentelemetry/instrumentation';

export function setupTracing() {
    // Enable OpenTelemetry diagnostic logging
    diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO);

    // Create a resource with service information
    const resource = new Resource({
        [ATTR_SERVICE_NAME]: process.env.SERVICE_NAME || 'tracer-app',
        [ATTR_SERVICE_VERSION]: process.env.npm_package_version || '1.0.0',
    });

    const otlpExporter = new OTLPTraceExporter({
        url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4318/v1/traces',
    });


    // Create tracer provider with resource and span processors
    const provider = new NodeTracerProvider({
        resource,
        spanProcessors: [
            new BatchSpanProcessor(otlpExporter, {
                maxQueueSize: 100,
                scheduledDelayMillis: 5000,
                exportTimeoutMillis: 30000,
                maxExportBatchSize: 50,
            })
        ]
    });

    // Register instrumentations with more comprehensive coverage
    registerInstrumentations({
        tracerProvider: provider,
        instrumentations: [
            new HttpInstrumentation({
                requestHook: (span, request) => {
                    span.setAttribute('http.request.method', request.method);
                },
            }),
            new NetInstrumentation(),
            new ExpressInstrumentation(),
            new PrismaInstrumentation({ middleware: true }),
        ],
    });

    // Register the provider
    provider.register();

    // Return the provider for potential manual instrumentation
    return provider;
}

// Call this at application startup
setupTracing();
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

توضیح

  • ثبت تشخیصی: ورود به سیستم تشخیصی را با استفاده از ثبت‌کننده کنسول در قسمت فعال می‌کند INFO سطح برای رفع اشکال تنظیم ردیابی.

  • اولیه سازی منابع:

    • متادیتا را درباره سرویس تعریف می کند، مانند SERVICE_NAME و SERVICE_VERSION.
    • این ابرداده به هر ردیابی متصل می شود و به شناسایی خدماتی که ردیابی متعلق است کمک می کند.
  • صادرکننده ردیابی OTLP: پروتکل OpenTelemetry را پیکربندی می کند (OTLP) صادر کننده برای ارسال داده های ردیابی به باطن که در حال حاضر Jaeger از آن استفاده می کند HTTP پروتکل توجه داشته باشید که Jaeger را می توان با باطن دیگری مانند Honeycomb، Zipkin و غیره تعویض کرد و شما پروتکل را به کارآمدتر تغییر دهید. GRPC از جاری HTTP.

  • ارائه دهنده Tracer با پردازنده Span: a را ایجاد می کند NodeTracerProvider، که ردیاب ها و گستره های زیر را مدیریت می کند:

  • ثبت ابزار دقیق: به طور خودکار ردیابی کتابخانه ها و چارچوب ها را می گیرد:

    • HttpInstrumentation: زمان صرف شده توسط درخواست ها/پاسخ های HTTP را ثبت می کند.
    • NetInstrumentation: زمان صرف شده توسط رویدادهای شبکه سطح پایین را ثبت می کند.
    • ExpressInstrumentation: زمان مصرف شده توسط میان افزار Express و مسیرها را ردیابی می کند.
    • PrismaInstrumentation: زمان صرف شده توسط پرسش های SQL تولید شده توسط Prisma را ردیابی می کند

کد ابزار دقیق را در برنامه خود وارد کنید

پیکربندی ردیابی را در فایل برنامه اصلی خود وارد کرده و مقداردهی اولیه کنید main.ts:

import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';
import './tracing';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const config = new DocumentBuilder()
    .setTitle('Tracing example')
    .setDescription('The tracing API description')
    .setVersion('1.0')
    .addTag('tracing')
    .build();
  const documentFactory = () => SwaggerModule.createDocument(app, config);
  SwaggerModule.setup('api-docs', app, documentFactory);

  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

برنامه خود را اجرا کنید

pnpm run start:dev
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

تنظیم Jaeger برای محیط توسعه

ساده ترین راه برای راه اندازی Jaeger این است که وینچ docker-compose در محیط توسعه به خوبی کار می کند.

ایجاد کنید docker-compose.yaml فایل:

services:
  jaeger:
    image: jaegertracing/all-in-one:1.63.0
    container_name: jaeger
    environment:
      COLLECTOR_OTLP_ENABLED: "true"
    ports:
      - "4317:4317" # For Jaeger-GRPC
      - "4318:4318" # For Jaeger-HTTP
      - "16686:16686" # # Web UI

networks:
  default:
    driver: bridge
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

برنامه Containerize (اختیاری)

می توانید استفاده کنید docker init اگر نسخه جدیدتر Docker را نصب کرده باشید، دستور تولید خودکار یک Dockerfile بهینه شده را صادر کنید.

# Arguments for versions
ARG NODE_VERSION=20.18.0
ARG PNPM_VERSION=9.12.2
ARG ALPINE_VERSION=3.20

################################################################################
# Base stage: Build the application
FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION} AS builder

# Set working directory
WORKDIR /usr/src/app

# Install pnpm globally with cache
RUN --mount=type=cache,target=/root/.npm \
    npm install -g pnpm@${PNPM_VERSION}

# Copy package.json and pnpm-lock.yaml to install dependencies
COPY ../package.json pnpm-lock.yaml ./

# Install dependencies with cache
RUN --mount=type=cache,target=/root/.pnpm-store \
    pnpm install --frozen-lockfile

# Copy the all application code
COPY .. .

# Setup prisma
RUN pnpm prisma generate

# Build the application
RUN pnpm run build

# Runner Stage
FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION} AS runner

# Set working directory
WORKDIR /usr/src/app

# Copy the built application from the builder stage
COPY --from=builder /usr/src/app/dist ./dist
COPY ../package.json pnpm-lock.yaml ./
COPY ../prisma/schema.prisma ./prisma/schema.prisma

# Install pnpm globally
RUN --mount=type=cache,target=/root/.npm \
    npm install -g pnpm@${PNPM_VERSION}

# Install dependencies with cache
RUN --mount=type=cache,target=/root/.pnpm-store \
    pnpm install --frozen-lockfile --prod

# Set NODE_ENV to production
ENV NODE_ENV=production

# Run the application
CMD ["pnpm", "run", "start:prod"]
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

Swagger UI

از http://localhost:3000/api-docs دیدن کنید و چند تماس API برقرار کنید

توضیحات تصویر

تجسم آثار

مرورگر خود را باز کنید و به http://localhost:16686 برای دیدن رابط کاربری Jaeger. چند درخواست را اجرا کنید و کلیک کنید ردیابی را پیدا کنید و روی یک Trace کلیک کنید

توضیحات تصویر

توضیحات تصویر

توضیحات تصویر

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا