مراقبة Claude Agent SDK باستخدام OpenTelemetry (2026)
١٥ يوليو ٢٠٢٦

يمكن لـ Claude Agent SDK تصدير كل طلب نموذج، واستدعاء أداة، وتنفيذ خطاف (hook) في تشغيل العميل على شكل تتبعات OpenTelemetry — ولكن التتبع يكون متوقفاً بشكل افتراضي ومقيداً بعلامة بيتا (beta flag)، كما أن شجرة النطاقات (span tree) التي ينتجها لها نظام تسمية خاص يتداخل جزئياً فقط مع الاصطلاحات الدلالية لـ OpenTelemetry GenAI12. يقوم هذا الدرس التعليمي بتفعيل التتبع في TypeScript، وقراءة التسلسل الهرمي الحقيقي للنطاقات، وربطه بنظام خلفي (backend) يمكنك معاينته فعلياً.
ملخص
قم بضبط CLAUDE_CODE_ENABLE_TELEMETRY=1 و CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1، ووجه OTEL_TRACES_EXPORTER=otlp نحو نقطة نهاية للمجمع (collector endpoint)، وعندها تصبح كل دورة للعميل عبارة عن نطاق جذر claude_code.interaction مع نطاقات فرعية لكل طلب نموذج (claude_code.llm_request)، وكل استدعاء أداة (claude_code.tool، مع نطاقات فرعية لـ .blocked_on_user و .execution)، وكل خطاف (claude_code.hook، المقيد ببيتا بشكل منفصل)12. هناك خمس سمات في تلك النطاقات — gen_ai.system، و gen_ai.request.model، و gen_ai.response.id، و gen_ai.response.finish_reasons، و gen_ai.tool.call.id — موثقة صراحةً كسمات اصطلاحية دلالية لـ OpenTelemetry GenAI؛ أما كل شيء آخر، بما في ذلك أسماء النطاقات نفسها، فيتبع مساحة الأسماء الخاصة بـ Anthropic وهي claude_code.*2. بيئة التشغيل: Node 18+ (تم البناء وفحص الأنواع مقابل Node 22.22.3). SDK: @anthropic-ai/claude-agent-sdk@0.3.2103. وقت البناء: 25-30 دقيقة.
ما ستتعلمه
- أي متغيرات البيئة تقوم بتفعيل التتبعات والمقاييس وأحداث السجلات بشكل مستقل، ولماذا تحتاج التتبعات إلى علامة واحدة لا تحتاجها الإشارتان الأخريان1
- التسلسل الهرمي الدقيق للنطاقات التي يصدرها Agent SDK، وصولاً إلى مستوى السمات، وأي السمات تظهر فقط عندما تختار التقاط المحتوى2
- كيفية بناء عميل بسيط لاستدعاء الأدوات يستحق التتبع، وتوجيه بيانات التتبع الخاصة به إلى مستقبل محلي يمكنك قراءته فعلياً
- كيفية جعل نطاق
claude_code.interactionالخاص بـ Claude Agent SDK يظهر كنطاق فرعي لتتبع تطبيقك الخاص، بدلاً من أن يكون جذراً منفصلاً - ما إذا كان تتبع Claude Agent SDK يتبع بالفعل الاصطلاحات الدلالية لـ OpenTelemetry GenAI، وماذا يعني هذا التمييز بالنسبة للوحات البيانات (dashboards) الخاصة بك
- كيفية الحصول على التكلفة وزمن الاستجابة لكل طلب بطريقتين مختلفتين — إحداهما لا تحتاج إلى OpenTelemetry على الإطلاق — ومتى تستخدم كل منهما
المتطلبات الأساسية
- Node.js 18.0.0 أو أحدث — لا يزال ملف
package.jsonالخاص بـ SDK يحدد"engines": {"node": ">=18.0.0"}في الإصدار الحالي؛ تم بناء هذا الدرس وفحص أنواعه مقابل Node 22.22.33 @anthropic-ai/claude-agent-sdk@0.3.210(الإصدارlatestالمؤكد في السجل وقت كتابة هذا النص، والمنشور في 2026-07-14)، بالإضافة إلى التبعيات المجاورةzod@^4.0.0، و@anthropic-ai/sdk@>=0.93.0، و@modelcontextprotocol/sdk@^1.29.03- TypeScript 5.x أو أحدث مع تفعيل وضع
strict(تم فحص أنواع كود هذا الدرس باستخدام TypeScript 7.0.2، وهي علامةlatestعلى npm وقت كتابة هذا النص) - مفتاح
ANTHROPIC_API_KEYإذا كنت ترغب في تشغيل حلقة العميل من البداية إلى النهاية ورؤية النطاقات الحقيقية تصل إلى المجمع — الكود أدناه تم فحص أنواعه مقابل SDK الحقيقي، ولكن خط أنابيب الكتابة الآلي هذا لا يملك مفتاح API مدفوع، لذا لم يتم تنفيذ استدعاءات نماذج حية؛ راجع قسم "التحقق" لمعرفة ما تم تشغيله وما لم يتم تشغيله بالضبط
لماذا لا يحل total_cost_usd هذه المشكلة بالفعل؟
يتضمن تدفق استجابة Agent SDK بالفعل رسالة result تحتوي على total_cost_usd، و duration_ms، واستهلاك الرموز (tokens) لكل استدعاء query() بالكامل4. هذا يكفي لتسجيل "هذا التشغيل كلف 0.03 دولار". لكنه لا يكفي للإجابة على "أي استدعاء أداة واحد في هذا التشغيل كان مسؤولاً عن معظم زمن الاستجابة"، أو "هل اضطر النموذج لإعادة المحاولة قبل أن ينجح استدعاء أداة معين"، أو "هل أداة خادم MCP معين هي الجزء البطيء باستمرار في كل تشغيل". هذه الأسئلة تتطلب نطاقاً (span) لكل خطوة، وليس رقماً إجمالياً واحداً لكل تشغيل — وهذا بالضبط ما يضيفه تتبع OpenTelemetry فوق رسالة النتيجة، وليس بدلاً منها.
الخطوة 1: إعداد المشروع
mkdir agent-observability && cd agent-observability
npm init -y
npm install @anthropic-ai/claude-agent-sdk@0.3.210 zod@4
npm install -D TypeScript@7 @types/node@26
npx tsc --init --target ES2022 --module NodeNext --moduleResolution NodeNext --strict
أضف "type": "module" إلى package.json — حيث يتم شحن SDK نفسه كـ "type": "module"، ويتوقع tsc --moduleResolution NodeNext أن يتطابق المشروع المستهلك معه3.
الخطوة 2: تفعيل إشارات التتبع الثلاث
المقاييس، وأحداث السجلات، والتتبعات هي ثلاث إشارات مستقلة، لكل منها مفتاح تصدير خاص، وجميعها متوقفة حتى تقوم بتفعيل المفتاح الرئيسي1:
// otel-env.ts
export const otelEnv = {
...process.env,
// Master switch — nothing exports without this
CLAUDE_CODE_ENABLE_TELEMETRY: "1",
// Traces specifically are beta and need this second flag;
// metrics and log events do not.
CLAUDE_CODE_ENHANCED_TELEMETRY_BETA: "1",
OTEL_TRACES_EXPORTER: "otlp",
OTEL_METRICS_EXPORTER: "otlp",
OTEL_LOGS_EXPORTER: "otlp",
OTEL_EXPORTER_OTLP_PROTOCOL: "http/json",
OTEL_EXPORTER_OTLP_ENDPOINT: "http://localhost:4318",
OTEL_SERVICE_NAME: "weather-agent-demo",
};
هناك أمران من السهل الخطأ فيهما هنا. أولاً، لا تضع console كقيمة للمصدر عند التشغيل من خلال SDK — لأن SDK يستخدم stdout كقناة رسائل خاصة به، لذا فإن مخرجات مصدر console ستتداخل مع (وقد تفسد) البروتوكول الذي يقرأه1. بدلاً من ذلك، وجه OTEL_EXPORTER_OTLP_ENDPOINT نحو مستقبل محلي حقيقي، وهو بالضبط ما تقوم الخطوة 5 ببنائه.
ثانياً، في TypeScript، أي شيء تمرره كـ options.env يستبدل بيئة العملية الفرعية بالكامل بدلاً من الدمج معها — تعريف الأنواع الخاص بـ SDK المثبت يوضح ذلك حرفياً: "هذه القيمة تستبدل بيئة العملية الفرعية بالكامل — لا يتم دمجها مع process.env"4. لهذا السبب يقوم otelEnv أعلاه بنشر ...process.env أولاً؛ إذا تخطيت ذلك، ستفقد العملية الفرعية PATH و HOME و ANTHROPIC_API_KEY. يتصرف ClaudeAgentOptions.env في Python بشكل مختلف — حيث يدمج البيانات فوق البيئة الموروثة بدلاً من استبدالها1.
الخطوة 3: إعطاء العميل أداة تستحق التتبع
استعلام "hello world" من دورة واحدة ينتج فقط نطاق llm_request واحد. لرؤية التسلسل الهرمي الكامل — بما في ذلك نطاقات الأدوات — يحتاج العميل إلى استدعاء أداة واحدة على الأقل. هذه أداة طقس بسيطة تم بناؤها باستخدام مساعدات tool() و createSdkMcpServer() الخاصة بـ SDK:
query() بجانب بيئة القياس عن بُعد (telemetry) من الخطوة 2:
// index.ts
import { query } from "@anthropic-ai/claude-agent-sdk";
import { otelEnv } from "./otel-env.js";
import { weatherServer } from "./weather-tool.js";
async function main() {
for await (const message of query({
prompt: "What's the weather like in Berlin?",
options: {
model: "claude-opus-4-8",
mcpServers: { weather: weatherServer },
allowedTools: ["mcp__weather__get_weather"],
env: otelEnv,
},
})) {
if (message.type === "assistant") {
console.log(message.message.content);
}
if (message.type === "result" && message.subtype === "success") {
console.log("total_cost_usd:", message.total_cost_usd);
console.log("duration_ms:", message.duration_ms);
}
}
}
main();
يعمل هذا على Claude Opus 4.8 (claude-opus-4-8) — تشير إرشادات Anthropic نفسها إلى "البدء بـ Claude Opus 4.8 للبرمجة الوكيلية المعقدة وأعمال المؤسسات" إذا كنت غير متأكد من النموذج الذي يجب استخدامه5.
الخطوة 4: قراءة شجرة الـ span
مع تفعيل التتبع (tracing)، ينتج عن دورة مستخدم واحدة هذا التسلسل الهرمي — هذه هي الشجرة الموثقة، مأخوذة حرفياً من مرجع المراقبة2:
claude_code.interaction
├── claude_code.llm_request
├── claude_code.hook (requires detailed beta tracing)
└── claude_code.tool
├── claude_code.tool.blocked_on_user
├── claude_code.tool.execution
└── (Agent tool) subagent claude_code.llm_request / claude_code.tool spans
claude_code.interaction هو الجذر: واحد لكل دورة مستخدم، ويحمل user_prompt_length، و interaction.sequence، و interaction.duration_ms. أما claude_code.llm_request فيغلف كل استدعاء لـ Claude API وهو المكان الذي توجد فيه السمات المتعلقة بالتكلفة وزمن الاستجابة: model، و duration_ms، و ttft_ms (الوقت حتى أول توكن)، و input_tokens، و output_tokens، و cache_read_tokens، و cache_creation_tokens، و stop_reason، و success2.
claude_code.tool يغلف كل استدعاء للأداة مع tool_name، و duration_ms، و result_tokens، وينقسم إلى اثنين من الأبناء: claude_code.tool.blocked_on_user (الوقت المستغرق في انتظار قرار الإذن، مع decision: accept|reject) و claude_code.tool.execution (التشغيل الفعلي، مع success/error)2. ثلاثة أنواع فقط من الـ spans تحمل حالة ERROR — وهي llm_request، و tool.execution، و hook — وكل شيء آخر ينتهي دائماً بـ UNSET حتى عند الفشل، لأن الفشل في تلك الـ spans يتم التعبير عنه من خلال الـ spans الأبناء الخاصة بها بدلاً من ذلك2.
claude_code.hook حالة خاصة: فهي تتطلب CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 بالإضافة إلى متغيرين إضافيين، ENABLE_BETA_TRACING_DETAILED=1 و BETA_TRACING_ENDPOINT. في جلسات CLI التفاعلية، تتطلب أيضاً أن تكون مؤسستك مدرجة في القائمة المسموح بها لهذه الميزة — ولكن Agent SDK وجلسات -p غير التفاعلية ليست مقيدة بشرط القائمة المسموح بها هذا2، لذا فإن الـ hook spans متاحة لمستهلكي SDK اليوم حتى في الأماكن التي لم تتوفر فيها بعد لكل من يستخدم CLI مباشرة.
هل تتبع هذه الـ spans الاصطلاحات الدلالية لـ OpenTelemetry GenAI؟
جزئياً، ومن الجدير بالذكر أن نكون دقيقين بشأن الأجزاء. أسماء الـ spans — claude_code.interaction، و claude_code.llm_request، و claude_code.tool — هي مساحة الأسماء الخاصة بـ Anthropic، وليست مخطط التسمية الخاص باصطلاحات GenAI؛ فالاصطلاحات نفسها تحدد أسماء spans قائمة على العملية بدلاً من ذلك، مثل invoke_agent لتشغيل الوكيل، و chat لاستدعاء LLM، و execute_tool لاستدعاء أداة6. تظل هذه الاصطلاحات في حالة تجريبية/تطويرية اعتباراً من منتصف عام 2026 وقد انتقلت إلى مستودع semantic-conventions-genai مخصص، منفصل عن مواصفات OpenTelemetry الرئيسية6. ولكن مجموعة فرعية محددة ومسماة من السمات (attributes) في spans الخاصة بـ claude_code.* في Claude Agent SDK موثقة صراحةً على أنها تطبق اصطلاح GenAI بغض النظر عن عدم تطابق اسم الـ span:
| Span | سمات خاصة بـ Anthropic (أمثلة) | سمات الاصطلاح الدلالي لـ OpenTelemetry GenAI |
|---|---|---|
claude_code.llm_request | model, duration_ms, ttft_ms, input_tokens, output_tokens, stop_reason, query_source | gen_ai.system (دائماً "anthropic"), gen_ai.request.model, gen_ai.response.id, gen_ai.response.finish_reasons |
claude_code.tool / .execution | tool_name, result_tokens, file_path, full_command | gen_ai.tool.call.id |
المصدر: جداول السمات في مرجع المراقبة، والتي تصنف كل سمة من هذه السمات الخمس على أنها "OpenTelemetry GenAI semantic convention" بشكل فردي2. النتيجة العملية: أي نظام خلفي يعرض لوحات البيانات بناءً على أسماء spans اصطلاح GenAI فقط لن يتعرف على claude_code.llm_request كعملية GenAI بشكل تلقائي، لأن الاسم نفسه ليس جزءاً من ذلك الاصطلاح — ولكنه سيجد السمات الخمس gen_ai.* إذا قام بالاستعلام عن طريق السمة بدلاً من اسم الـ span. لا تفترض الامتثال الكامل، ولا تفترض انعدام العلاقة أيضاً.
إحدى هذه السمات الخمس تستحق تنبيهاً خاصاً: gen_ai.system لا تظهر في أي مكان في سجل سمات OpenTelemetry GenAI الحالي — فقد قامت المواصفات منذ ذلك الحين بتغيير اسمها إلى gen_ai.provider.name لنفس الغرض وهو "أي مزود تعامل مع هذا الاستدعاء"6.
لا تزال وثائق المراقبة الخاصة بـ Anthropic تصنف gen_ai.system كسمة اصطلاح GenAI التي يصدرها Claude Code، مما يعني إما أن الـ span الخاص بـ CLI لا يزال يحمل هذا المفتاح حرفياً، أو أن وثائق Anthropic نفسها لم يتم تحديثها إلى اسم السمة الحالي — هذا المنشور ينقل ما تقوله وثائق Anthropic و SDK فعلياً اليوم، وليس ما تطلبه أحدث مراجعة للمواصفات، لذا لا تفترض أن gen_ai.system هو اسم المفتاح الذي يتوقعه أي نظام خلفي أو لوحة بيانات GenAI محدثة وفقاً للمواصفات.
إذا كنت تفضل إعداد حلقة استدعاء أدوات يدوية مباشرة وفقاً لاصطلاحات GenAI الحالية — spans invoke_agent/chat/execute_tool و gen_ai.provider.name بدلاً من تصدير claude_code.* الخاص بـ Agent SDK — فإن درس تتبع الاصطلاحات الدلالية لـ OpenTelemetry GenAI يبني ذلك بالضبط، من Messages API الخام بدلاً من Agent SDK.
الخطوة 5: رؤية spans حقيقية دون الدفع مقابل نظام خلفي
لست بحاجة إلى Jaeger أو مورد تجاري لفحص ما يرسله CLI فعلياً — خادم HTTP بسيط يقوم بتسجيل جسم الطلب (request body) يفي بالغرض، طالما أنك تستخدم بروتوكول http/json (JSON قابل للقراءة) بدلاً من http/protobuf:
// receiver.ts
import http from "node:http";
const server = http.createServer((req, res) => {
if (req.method === "POST" && req.url === "/v1/traces") {
let body = "";
req.on("data", (chunk) => (body += chunk));
req.on("end", () => {
const parsed = JSON.parse(body);
for (const rs of parsed.resourceSpans ?? []) {
for (const ss of rs.scopeSpans ?? []) {
for (const span of ss.spans ?? []) {
console.log(`${span.name}`);
}
}
}
res.writeHead(200, { "Content-Type": "application/json" });
res.end("{}");
});
} else {
res.writeHead(404);
res.end();
}
});
server.listen(4318, () => {
console.log("Listening on http://localhost:4318/v1/traces");
});
قم بتشغيله مباشرة باستخدام node receiver.ts — إصدارات Node 22.18.0 وما بعدها تقوم بتشغيل ملفات TypeScript التي تحتوي على بناءات قابلة للمسح (erasable syntax) مثل هذا الملف بشكل أصلي، دون الحاجة إلى علامة (flag) أو خطوة تجميع. في الإصدارات من Node 22.6.0 إلى 22.17.x، أضف العلامة صراحةً: node --experimental-strip-types receiver.ts. الإصدارات الأقدم، بما في ذلك جميع إصدارات Node 18.x و 20.x، لا تدعم تشغيل ملفات .ts بشكل أصلي على الإطلاق — استخدم npx tsx receiver.ts أو قم بالتجميع باستخدام tsc أولاً7.
بعد ذلك، قم بتشغيل index.ts من الخطوة 3 ضده. بالنسبة للاستخدام في بيئة الإنتاج، استبدل هذا بـ collector فعلي أو حاوية Jaeger شاملة — الإرشادات الرسمية صريحة في أن هذا النوع من المستمعات البسيطة مخصص للفحص المحلي، وليس لتشغيل خط أنابيب حقيقي1.
الخطوة 6: ربط التتبع بـ span الخاص بتطبيقك
// traced-request.ts
import { trace } from "@opentelemetry/API";
import { query } from "@anthropic-ai/claude-agent-sdk";
import { otelEnv } from "./otel-env.js";
const tracer = trace.getTracer("weather-agent-demo");
export async function handleUserRequest(prompt: string) {
return tracer.startActiveSpan("handle-user-request", async (span) => {
try {
for await (const message of query({
prompt,
options: { model: "claude-opus-4-8", env: otelEnv },
})) {
if (message.type === "result" && message.subtype === "success") {
span.setAttribute("total_cost_usd", message.total_cost_usd);
}
}
} finally {
span.end();
}
});
}
يتم تخطي الحقن التلقائي إذا قمت بتعيين TRACEPARENT صراحةً في options.env بنفسك — وهذا مفيد إذا كنت بحاجة إلى تثبيت سياق أب محدد بدلاً من أي span نشط حالياً. تتجاهل جلسات CLI التفاعلية TRACEPARENT الواردة تماماً لتجنب التقاط قيم محيطة من CI أو الحاويات؛ فقط تشغيل Agent SDK و claude -p هما من يلتزمان بها12. هناك حد للنطاق آخر يستحق المعرفة: يتم إرسال رأس traceparent إلى Anthropic API مباشرة بشكل افتراضي. إذا كنت تقوم بالتوجيه عبر بروكسي ANTHROPIC_BASE_URL مخصص، فإن الانتشار (propagation) يكون متوقفاً ما لم تقم أيضاً بتعيين CLAUDE_CODE_PROPAGATE_TRACEPARENT=1، لأن بعض البروكسيات ترفض الرؤوس غير المعروفة2.
الخطوة 7: وسم التتبعات حسب الخدمة والمستخدم والمستأجر
الاسم الافتراضي لـ service.name في كل span هو claude-code1. إذا كان هناك أكثر من عميل (agent) في نظامك يقوم بالتصدير إلى نفس الـ collector، قم بتجاوزه وإرفاق أي بيانات وصفية تحتاجها للتصفية بها لاحقاً:
const options = {
env: {
...otelEnv,
OTEL_SERVICE_NAME: "support-triage-agent",
OTEL_RESOURCE_ATTRIBUTES:
`service.version=1.4.0,deployment.environment=production,enduser.id=${encodeURIComponent(userId)},tenant.id=${encodeURIComponent(tenantId)}`,
},
};
قم بتشفير قيم السمات بنسبة مئوية (Percent-encode) قبل دمجها — حيث يحتفظ OTEL_RESOURCE_ATTRIBUTES بالفواصل والمسافات وعلامات التساوي كمحددات خاصة به1. مع إرفاق enduser.id/tenant.id، تصبح أحداث السجل الخاصة بـ tool_decision و tool_result و mcp_server_connection و permission_mode_changed بمثابة سجل مراجعة لكل مستخدم يمكنك توجيهه إلى SIEM، بدلاً من مجرد نسب كل إجراء إلى أي بيانات اعتماد تعمل تحتها العملية1.
التحكم فيما يتم التقاطه
تكون الـ spans هيكلية بشكل افتراضي — يتم دائماً تسجيل المدد وأسماء النماذج وأسماء الأدوات، ولكن لا يتم تسجيل عدد التوكنات إلا عندما تعيد مكالمة API الأساسية بيانات الاستخدام فعلياً (لذا قد يخلو span الخاص بطلب فاشل أو ملغى منها ببساطة)، ولا يتم أبداً التقاط المحتوى الذي يقرأه ويكتبه العميل الخاص بك إلا إذا اخترت ذلك12:
| المتغير | ما الذي يضيفه |
|---|---|
OTEL_LOG_USER_PROMPTS=1 | نص المطالبة (Prompt) في سمة user_prompt الخاصة بـ span الـ claude_code.interaction |
OTEL_LOG_TOOL_DETAILS=1 | وسائط إدخال الأداة — مسارات الملفات، أوامر shell، أنماط البحث — في claude_code.tool |
OTEL_LOG_TOOL_CONTENT=1 | أجسام إدخال/إخراج الأداة الكاملة كحدث span، يتم قصها عند 60 كيلوبايت؛ تتطلب أن يكون التتبع مفعلاً بالفعل |
OTEL_LOG_RAW_API_BODIES | JSON كامل لطلب/استجابة Messages API؛ القيمة 1 تقص المحتوى داخلياً عند 60 كيلوبايت، و file:<dir> تكتب الأجسام غير المقصوصة على القرص |
اترك المتغيرات الأربعة بدون تعيين ما لم يكن خط أنابيب المراقبة الخاص بك معتمداً خصيصاً لتخزين ما يتعامل معه العميل الخاص بك — تفعيل متغير الأجسام الخام (raw-bodies) يعني الموافقة على كل ما قد تكشفه المتغيرات الثلاثة الأخرى بشكل منفصل1.
إرسال التتبعات إلى backend حقيقي
هناك مساران مختلفان تماماً يوصلانك إلى لوحة تحكم، وهما يقومان بتجهيز أشياء مختلفة:
| تصدير CLI الأصلي (هذا البرنامج التعليمي) | تجهيز OpenInference | |
|---|---|---|
| ما الذي يتم تجهيزه فعلياً | العملية الفرعية لـ Claude Code CLI التي يقوم الـ SDK بتشغيلها | مكالمات SDK الخاصة بعمليتك، والتي يتم تعديلها بواسطة مكتبة تجهيز JS/Python |
| أسماء الـ spans التي تحصل عليها | claude_code.* | شكل span الخاص بـ OpenInference |
| الحزم الإضافية المطلوبة | لا يوجد — فقط متغيرات البيئة | @arizeai/openinference-instrumentation-claude-agent-sdk, @langfuse/otel, @opentelemetry/sdk-node8 |
| أين يمكن أن تذهب | أي backend متوافق مع OTLP: Honeycomb, Datadog, Grafana, collector مستضاف ذاتياً، أو Langfuse1 | أي exporter تربط به الـ NodeSDK — توضح وثائق Langfuse هذا المسار تحديداً8 |
مسار OpenInference (المستخدم في تكامل JS/TS الرسمي لـ Langfuse) هو طبقة تجهيز مجتمعية منفصلة، وليس تكويناً بديلاً لنفس التصدير على مستوى CLI الذي يبنيه هذا البرنامج التعليمي — يمكن للاثنين التعايش، ولكن لا تتوقع منهما إنتاج أشجار span متطابقة8. إذا كان هدفك هو مراجعة المطالبات/الاستجابات، والتقييم، وتنسيق مجموعات البيانات، فإن مسار OpenInference-to-Langfuse مصمم لذلك. أما إذا كان هدفك هو لوحات تحكم بأسلوب العمليات (ops)، أو توزيع التكاليف لكل مستأجر، أو سجل مراجعة قابل للتوجيه إلى SIEM، فإن تصدير CLI الأصلي الذي يغطيه هذا البرنامج التعليمي هو المسار الأكثر مباشرة، لأنه لا يتطلب إضافة تبعية تجهيز إلى عمليتك على الإطلاق.
التكلفة وزمن الاستجابة بدون كل هذا
إذا كان كل ما تحتاجه هو رقم واحد لكل عملية تشغيل، فأنت لست بحاجة إلى OpenTelemetry على الإطلاق. رسالة result في تدفق استجابة الـ SDK نفسه تحمل بالفعل total_cost_usd و duration_ms و num_turns وتفصيلاً كاملاً لـ usage/modelUsage لمكالمة query() بالكامل، دون الحاجة إلى أي تكوين للمصدر (exporter)4. التتبع مخصص للحالات التي لا يكون فيها هذا الرقم الإجمالي كافياً لمعرفة أي خطوة كانت مكلفة — الاثنان يكملان بعضهما البعض ولا يتنافسان، ودليل المراقبة الخاص بـ Anthropic يربط بوثيقة تتبع التكاليف لهذا السبب تحديداً1.
التحقق
ما تم تشغيله فعلياً: تم تثبيت كل عينة كود أعلاه مقابل الإصدار الحقيقي والمنشور حالياً @anthropic-ai/claude-agent-sdk@0.3.210 (سجل npm، تم التأكد في 2026-07-15) بالإضافة إلى zod@4.4.3، و @types/node@26.1.1، و TypeScript@7.0.2، و @opentelemetry/API@1.9.1، وتم فحص الأنواع باستخدام tsc --strict --target es2022 --module nodenext --moduleResolution nodenext. جميع ملفات TypeScript الخمسة — otel-env.ts، و weather-tool.ts، و index.ts، و traced-request.ts، و receiver.ts — تم تجميعها معاً دون أي أخطاء. تم تشغيل خادم receiver.ts فعلياً (node، يستمع على المنفذ 4318) وأرسل حمولة OTLP/JSON مصممة يدوياً عبر curl تحتوي على أسماء الـ spans الحقيقية من الخطوة 4؛ وقام المستقبل بتحليلها بشكل صحيح وطبع جميع أسماء الـ spans الثلاثة، مما يؤكد أن منطق التحليل يعمل مقابل شكل الحمولة الموثق.
ما لم يتم تشغيله: استدعاء query() مباشر مقابل API الحقيقي لـ Claude. يتطلب ذلك ANTHROPIC_API_KEY مدفوع، وهو ما لا يملك خط أنابيب الكتابة الآلي هذا وصولاً إليه، لذا لم يتم التقاط أي spans حقيقية لـ claude_code.* من تشغيل وكيل فعلي — شجرة الـ spans وأسماء السمات في هذا المنشور منقولة مباشرة من المرجع الموثق الخاص بـ Anthropic، وليس من عملية التقاط مستقلة2. إذا كان لديك مفتاح، قم بتشغيل:
export ANTHROPIC_API_KEY=sk-ant-...
npx tsx index.ts
يجب أن ترى إجابة الطقس مطبوعة، تليها total_cost_usd و duration_ms من رسالة result، و — إذا كان receiver.ts يعمل و OTEL_EXPORTER_OTLP_ENDPOINT يشير إليه — سطر claude_code.interaction تتبعه أسطر claude_code.llm_request و claude_code.tool في وحدة تحكم المستقبل نفسها.
استكشاف الأخطاء وإصلاحها
لا تصل أي spans إلى أي مكان. تأكد من ضبط كل من CLAUDE_CODE_ENABLE_TELEMETRY=1 و CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 — تتطلب التتبعات (traces) كليهما، على عكس المقاييس والسجلات التي تتطلب الأول فقط12.
تصل الـ spans ولكن كل سمة كنت تتوقعها مفقودة. التقاط المحتوى اختياري. إذا كانت user_prompt، أو مدخلات الأداة، أو أجسام الأدوات مفقودة، فهذا هو سلوك التعتيم الافتراضي وليس خطأً — قم بضبط متغير OTEL_LOG_* ذو الصلة من الجدول أعلاه1.
لا يظهر شيء في stdout، أو يبدو أن العملية تتوقف بشكل غريب. تأكد من أنك لم تضبط console كقيمة للمصدر (exporter) — يستخدم الـ SDK بالفعل stdout كقناة رسائل، وسيحدث تصادم بينهما1.
التتبعات لا تتداخل تحت الـ span الخاص بتطبيقي. تأكد من أن الـ span الخاص بك نشط فعلياً (عبر startActiveSpan، وليس مجرد إنشائه) في اللحظة التي يتم فيها استدعاء query()، وأنك لم تضبط TRACEPARENT صراحةً في options.env، مما يتجاوز الحقن التلقائي بالكامل1.
كل ما سبق يعمل محلياً ولكن لا يصل شيء عند التوجيه عبر URL أساسي مخصص. إذا كان ANTHROPIC_BASE_URL يشير إلى أي شيء آخر غير API الخاص بـ Anthropic مباشرة، فقم بضبط CLAUDE_CODE_PROPAGATE_TRACEPARENT=1 — حيث يكون التمرير إلى نقاط النهاية غير التابعة لـ Anthropic معطلاً بشكل افتراضي2.
الخطوات التالية وقراءات إضافية
يتتبع هذا البرنامج التعليمي الأدوات المدمجة والأدوات المحددة بواسطة SDK؛ وتطبق نفس نطاقات claude_code.tool على الأدوات المعروضة عبر MCP، وهو أمر يستحق الدمج مع البرنامج التعليمي لخادم MCP في بيئة الإنتاج مع OAuth و HTTP القابل للبث إذا كانت أدوات العميل الخاص بك تقع خلف نقطة نهاية MCP موثقة.
إذا كنت تقوم أيضًا بإضافة طبقة موافقة أمام استدعاءات الأدوات، فإن البرنامج التعليمي لـ Claude Agent SDK بنظام "الإنسان في الحلقة" (human-in-the-loop) يغطي استدعاء canUseTool وخطافات PreToolUse/PostToolUse لبناء سجل تدقيق خاص بك — وهي آلية مختلفة عن حدث سجل tool_decision الذي يغطيه قسم مسار التدقيق في هذا المنشور، لأن ذلك الحدث يتم إصداره تلقائيًا بمجرد تفعيل OTEL_LOGS_EXPORTER بدلاً من كتابته بواسطة خطاف تقوم بتطويره بنفسك، ولكن المكانين يكملان بعضهما البعض للبحث عن نفس النوع من سجلات "ماذا فعل العميل فعليًا".
وإذا كان العميل الخاص بك بحاجة إلى تذكر الأشياء عبر الجلسات بدلاً من مجرد أن يكون قابلاً للملاحظة داخل جلسة واحدة، فإن البرنامج التعليمي لأداة ذاكرة Claude وتحرير السياق يغطي ميزة الذاكرة المستمرة في SDK، وهي مسألة تتعلق بحزمة مختلفة تمامًا عن إعداد التتبع هنا.
الحواشي السفلية
-
Anthropic, "Observability with OpenTelemetry" — https://code.claude.com/docs/en/agent-sdk/observability (نموذج الإشارات الثلاث، أعلام
CLAUDE_CODE_ENABLE_TELEMETRY/CLAUDE_CODE_ENHANCED_TELEMETRY_BETA، سلوك الاستبدال مقابل الدمج في TypeScriptenv، انتشار سياق تتبع W3C،OTEL_SERVICE_NAME/OTEL_RESOURCE_ATTRIBUTES، متغيرات الموافقة على التقاط المحتوى؛ تم الجلب في 2026-07-15) ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17 ↩18 ↩19 ↩20 ↩21 ↩22 ↩23
Anthropic, "Monitoring" — https://code.claude.com/docs/en/monitoring-usage، قسم "Traces (beta)" (مخطط التسلسل الهرمي للـ span، جداول سمات كل span، سمات gen_ai.* المصنفة فرديًا كـ "OpenTelemetry GenAI semantic convention"، نطاق القائمة المسموح بها لـ claude_code.hook، CLAUDE_CODE_PROPAGATE_TRACEPARENT، دلالات حالة OTel؛ تم الجلب في 2026-07-15) ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17 ↩18 ↩19
npm registry, @anthropic-ai/claude-agent-sdk — https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk (الإصدار 0.3.210، نُشر في 2026-07-14T19:39:19.351Z وفقًا لإدخال time الخاص بسجل هذا الإصدار، engines.node >=18.0.0، إصدار claudeCodeVersion المدمج 2.1.210، التبعيات الموازية peerDependencies zod ^4.0.0/@anthropic-ai/sdk >=0.93.0/@modelcontextprotocol/sdk ^1.29.0؛ تم الجلب مباشرة من السجل API، 2026-07-15) ↩ ↩2 ↩3 ↩4
تعريفات الأنواع المجمعة لـ @anthropic-ai/claude-agent-sdk@0.3.210، sdk.d.ts — تم التثبيت من npm والفحص مباشرة، 2026-07-15 (تعليق دلالات الاستبدال لـ Options.env، توقيعات query()/tool()/createSdkMcpServer()، حقول SDKResultSuccess بما في ذلك total_cost_usd/duration_ms/usage/modelUsage) ↩ ↩2 ↩3 ↩4
Anthropic, "Models overview" — https://platform.claude.com/docs/en/about-claude/models/overview (معرفات النماذج الحالية والأسعار: Claude Opus 4.8 claude-opus-4-8 بسعر 5$/25$ لكل مليون توكن؛ تم الجلب في 2026-07-15) ↩
OpenTelemetry, "Generative AI semantic conventions" — https://opentelemetry.io/docs/specs/semconv/gen-ai/ (تنص الصفحة صراحةً على أن اتفاقيات GenAI الدلالية قد انتقلت إلى مستودع semantic-conventions-genai مخصص ولم تعد تُصان في مستودع المواصفات الرئيسي؛ تم الجلب في 2026-07-15). تم تأكيد حالة التطوير/التجريبية وقت كتابة هذا النص بشكل منفصل عبر منشور مدونة OpenTelemetry، "GenAI observability"، بتاريخ 14 مايو 2026 — https://opentelemetry.io/blog/2026/genai-observability/ ("قيد التطوير النشط")، وعبر أسماء الـ span مثل invoke_agent/chat/execute_tool التي يوثقها منشور المدونة كـ "مخطط تسمية" خاص باتفاقيات GenAI — وهو متميز عن claude_code.*. جدول السمات الكامل في نفس صفحة السجل (تم الجلب مباشرة، 2026-07-15) يسرد gen_ai.provider.name ولكن لا يحتوي على أي إدخال لـ gen_ai.system مجردة في أي مكان، سواء أبجديًا أو غير ذلك — فقط تظهر سمة gen_ai.system_instructions المتميزة — وهو ما يتفق مع إعادة تسمية gen_ai.system إلى gen_ai.provider.name في المواصفات الحالية. ↩ ↩2 ↩3 ↩4
Node.js, "Running TypeScript Natively" — https://nodejs.org/learn/TypeScript/run-natively (الإصدار v22.18.0+ يشغل ملفات .ts ذات القواعد القابلة للمسح بدون أي flag؛ الإصدارات الأقدم تحتاج إلى --experimental-strip-types؛ عملية تجريد الأنواع لا تقوم بفحص الأنواع، ولا يتم دعم enum/namespaces/parameter properties؛ تم الجلب في 2026-07-15) ↩
Langfuse, "Observability for Claude Agent SDK JS/TS with Langfuse" — https://langfuse.com/integrations/frameworks/claude-agent-sdk-js (قائمة أوامر التثبيت تشمل @anthropic-ai/claude-agent-sdk، و @arizeai/openinference-instrumentation-claude-agent-sdk، و @langfuse/otel، و @opentelemetry/sdk-node معاً؛ إعداد NodeSDK/LangfuseSpanProcessor؛ المثال القابل للتشغيل الموجود على الصفحة هو عبارة عن prompt بدون أدوات على claude-sonnet-4-5، وليس أداة طقس؛ تم الجلب في 2026-07-15) ↩ ↩2 ↩3 ↩4
Langfuse, "Pricing" — https://langfuse.com/pricing ("خطة Hobby مجانية تماماً ولا تتطلب بطاقة ائتمان،" تشمل 100 ألف وحدة/شهر) و Langfuse, "Self-Hosted Pricing" — https://langfuse.com/pricing-self-host (مستوى Community مدرج كـ "مجاني"، رخصة MIT، "جميع ميزات المنصة الأساسية و APIs"؛ تم جلب كليهما مباشرة، 2026-07-15) ↩



