পাঠ ৪১ · ৫১-এর মধ্যে · মডিউল ১০
Home / Courses / System Design / লগিং ও ট্রেসিং

লগিং, মনিটরিং ও ডিস্ট্রিবিউটেড ট্রেসিং

Logging, monitoring & distributed tracing
৮ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • অবজারভেবিলিটির তিন স্তম্ভ — লগ, মেট্রিক্স, ট্রেস — এবং প্রতিটি কোন প্রশ্নের উত্তর দেয়
  • স্ট্রাকচার্ড লগিং কেন ফ্রি-টেক্সট লগের চেয়ে ভালো স্কেল করে
  • ডিস্ট্রিবিউটেড ট্রেসিং কীভাবে span ও ট্রেস-আইডি দিয়ে একটি রিকোয়েস্টের পুরো যাত্রা পুনর্গঠন করে
  • Python দিয়ে একটি নেস্টেড কল-চেইনের ট্রেস সিমুলেট করা — span duration ও nesting depth রিপোর্ট করা

১ · অবজারভেবিলিটির তিন স্তম্ভ

একটি বড় ডিস্ট্রিবিউটেড সিস্টেমে "ভেতরে কী ঘটছে" বোঝার জন্য তিন ধরনের সিগন্যাল দরকার হয়, প্রতিটি ভিন্ন প্রশ্নের উত্তর দেয় —

লগিংLoggingবিচ্ছিন্ন ইভেন্টের (এরর, রিকোয়েস্ট, স্টেট পরিবর্তন) টাইমস্ট্যাম্পড রেকর্ড।
কী ঘটেছিল? — একটি নির্দিষ্ট এরর, একটি নির্দিষ্ট রিকোয়েস্টের বিস্তারিত।
মেট্রিক্সMetricsসময়ের সাথে অ্যাগ্রিগেটেড সাংখ্যিক পরিমাপ — রিকোয়েস্ট কাউন্ট, এরর রেট, লেটেন্সি পার্সেন্টাইল, CPU/মেমরি ব্যবহার।
কতটুকু/কতবার? — এরর রেট বাড়ছে কি না, p99 লেটেন্সি (L03) কেমন।
ট্রেসিংDistributed Tracingএকটি একক রিকোয়েস্ট একাধিক মাইক্রোসার্ভিস স্পর্শ করলে, প্রতিটি সার্ভিস কল-এর সময় রেকর্ড করে একটি সম্পূর্ণ কল-চেইন টাইমলাইন পুনর্গঠন করা।
কল-চেইনের কোথায়? — কোন সার্ভিসে সময় লাগছে, কোন ধাপে ধীরগতি।
তিনটাই কেন দরকার, একটা যথেষ্ট নয়

মেট্রিক্স বলবে "এরর রেট ৫% বেড়ে গেছে" — কিন্তু কোন নির্দিষ্ট রিকোয়েস্টে, কেন, তা বলবে না। লগ সেই নির্দিষ্ট এরর মেসেজ ও কনটেক্সট দেবে। কিন্তু মাইক্রোসার্ভিসে (L25) একটি রিকোয়েস্ট ৫টা সার্ভিস দিয়ে গেলে, কোন সার্ভিসে আসলে সময় বেশি লাগছে তা লগ/মেট্রিক্স দিয়ে বোঝা কঠিন — এখানেই ট্রেসিং দরকার, যা পুরো কল-চেইনের একটি ভিজ্যুয়াল টাইমলাইন দেয়।

২ · স্ট্রাকচার্ড লগিং

সাধারণ ফ্রি-টেক্সট লগ ("User 42 failed to login at 10:03") মানুষের পড়ার জন্য সহজ, কিন্তু লক্ষ লক্ষ লাইনের মধ্যে প্রোগ্রাম্যাটিকভাবে খোঁজা কঠিন। স্ট্রাকচার্ড (JSON) লগ ({"event":"login_failed","user_id":42,"ts":"..."}) প্রতিটি তথ্যকে একটি ফিল্ড হিসেবে রাখে — যাতে "সব login_failed ইভেন্ট যেখানে user_id=42" এর মতো প্রশ্ন সরাসরি কোয়েরি করা যায়, টেক্সট প্যাটার্ন-ম্যাচিং ছাড়াই। স্কেলে (কোটি কোটি লগ লাইন) এই পার্থক্যটাই লগিংকে ব্যবহারযোগ্য বনাম অকেজো করে দেয়।

৩ · ডিস্ট্রিবিউটেড ট্রেসিং — span ও ট্রেস-আইডি

যখন একটি ক্লায়েন্ট রিকোয়েস্ট একাধিক মাইক্রোসার্ভিস (L25) দিয়ে যায় (যেমন Gateway → Service A → Service B), প্রতিটি রিকোয়েস্টকে একটি ইউনিক trace ID দেওয়া হয় যা প্রতিটি সার্ভিস কলের সাথে প্রপাগেট হয়। প্রতিটি সার্ভিস তার নিজের কাজের একটি span (নাম + শুরু সময় + শেষ সময়) রেকর্ড করে এবং সেই span-টি trace ID-এর সাথে ট্যাগ করে পাঠিয়ে দেয় একটি কেন্দ্রীয় ট্রেসিং সিস্টেমে (Jaeger, Zipkin, OpenTelemetry)। সব span একত্র করলে পুরো রিকোয়েস্টের একটি "ওয়াটারফল" টাইমলাইন পুনর্গঠিত হয় — কোন সার্ভিস কতক্ষণ ধরে চলেছিল, এবং কোনটি কোনটির ভেতরে নেস্টেড ছিল।

handle_request (span ০, পুরো রিকোয়েস্ট) call_service_a (span ১, নেস্টেড) call_service_b (span ২, সবচেয়ে গভীরে) সময় →
প্রতিটি span-এর প্রস্থ তার duration নির্দেশ করে; নেস্টিং দেখায় কোন কল কোন কলের ভেতর থেকে হয়েছিল।

নিচের কোড সেলে একটি বাস্তব (fake-clock ভিত্তিক, রিপ্রোডিউসিবল) ট্রেস সিমুলেশন — handle_request কল করে call_service_a-কে, যা কল করে call_service_b-কে। প্রতিটি ফাংশন নিজের span একটি শেয়ার্ড তালিকায় রেকর্ড করে, একটি ম্যানুয়ালি-বাড়ানো ফেক-ক্লক কাউন্টার ব্যবহার করে।

Python
spans = []
clock = [0]   # ফেক মনোটোনিক ক্লক — লিস্টে রাখা হলো যাতে নেস্টেড ফাংশন থেকে বাড়ানো যায়

def tick():
    clock[0] += 1
    return clock[0]

def call_service_b():
    start = tick()
    tick(); tick()          # সিমুলেটেড কাজ
    end = tick()
    spans.append(("call_service_b", start, end, 2))   # depth 2 — সবচেয়ে গভীরে
    return end

def call_service_a():
    start = tick()
    call_service_b()
    end = tick()
    spans.append(("call_service_a", start, end, 1))    # depth 1
    return end

def handle_request():
    start = tick()
    call_service_a()
    end = tick()
    spans.append(("handle_request", start, end, 0))    # depth 0 — রুট span
    return end

handle_request()

# শুরুর সময় অনুযায়ী সাজিয়ে ট্রেস পুনর্গঠন করা
spans_sorted = sorted(spans, key=lambda s: s[1])

print(f"{'Span':<22}{'Start':<8}{'End':<8}{'Duration':<10}{'Depth'}")
for name, start, end, depth in spans_sorted:
    indent = "  " * depth
    label = indent + name
    print(f"{label:<22}{start:<8}{end:<8}{end-start:<10}{depth}")

    
লক্ষ্য করুন — handle_request-এর duration (৭) সবচেয়ে বড়, কারণ এটি বাকি দুটোকে ভেতরে ধারণ করে; call_service_b-এর duration (৩) সবচেয়ে ছোট, কারণ এটি সবচেয়ে ভেতরের/গভীরতম span। এটাই ঠিক সেই "ওয়াটারফল" প্যাটার্ন যা Jaeger/Zipkin-এর মতো টুলে দেখা যায় — বড় বার তার ভেতরের ছোট বারগুলোকে সময়ের দিক থেকে ধারণ করে।

৪ · মেট্রিক্স ও ড্যাশবোর্ড

লগ ও ট্রেস সাধারণত একটি নির্দিষ্ট সমস্যা তদন্ত করার জন্য ব্যবহৃত হয় (কিছু একটা ভুল হয়েছে জানার পর)। কিন্তু কিছু ভুল হচ্ছে তা প্রথমে জানতে হলে দরকার মেট্রিক্স — রিকোয়েস্ট কাউন্ট, এরর রেট, লেটেন্সি পার্সেন্টাইল (p50/p95/p99, L03), CPU/মেমরি ব্যবহার — সময়ের সাথে সংগৃহীত ও ড্যাশবোর্ডে ভিজ্যুয়ালাইজড। এই মেট্রিক্সের উপর ভিত্তি করেই অ্যালার্ট সেট করা হয় (যেমন "এরর রেট ৫%-এর বেশি হলে অন-কল ইঞ্জিনিয়ারকে জানাও") — যা L42-এ SLA/SLO/SLI-এর সাথে সরাসরি যুক্ত।

মূল কথা · Key takeaway

মেট্রিক্স বলে দেয় যে কিছু একটা ভুল হয়েছে, লগ বলে দেয় কী ভুল হয়েছে, আর ট্রেস বলে দেয় কোথায় (কল-চেইনের কোন সার্ভিসে) ভুল/ধীরগতি হয়েছে। মাইক্রোসার্ভিস আর্কিটেকচারে (L25) যত বেশি সার্ভিস, ততই এই তিন স্তম্ভ ছাড়া একটি সমস্যা ডিবাগ করা কার্যত অসম্ভব হয়ে ওঠে।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ একটি মনোলিথ অ্যাপ্লিকেশনে (L25) ডিস্ট্রিবিউটেড ট্রেসিং কেন খুব বেশি প্রয়োজন হয় না, কিন্তু মাইক্রোসার্ভিসে অপরিহার্য হয়ে ওঠে?

একটি মনোলিথে পুরো রিকোয়েস্ট একটি প্রসেসের ভেতরেই থাকে — একটি সাধারণ প্রোফাইলার বা স্ট্যাক ট্রেসই যথেষ্ট বলে দেয় কোথায় সময় লাগছে, কারণ কোনো নেটওয়ার্ক-বাউন্ডারি পার হতে হয় না। মাইক্রোসার্ভিসে একই রিকোয়েস্ট একাধিক আলাদা প্রসেসে (এমনকি আলাদা মেশিনে) ভাগ হয়ে যায় — কোনো একক প্রোফাইলার পুরো ছবি দেখতে পায় না। এখানেই একটি শেয়ার্ড trace ID প্রতিটি সার্ভিস-বাউন্ডারি পার করে প্রপাগেট করে সব টুকরো span একত্র করা দরকার হয় পুরো ছবি পুনর্গঠন করতে।

প্র ০২ ফ্রি-টেক্সট লগের বদলে স্ট্রাকচার্ড (JSON) লগ ব্যবহার করলে ঠিক কী সুবিধা পাওয়া যায় যা টেক্সট-সার্চে (grep) পাওয়া যায় না?

ফ্রি-টেক্সট লগে "সব লগইন-ব্যর্থতা যেখানে user_id=42" খুঁজতে হলে একটি রেজেক্স/প্যাটার্ন লিখতে হয় যা লগ মেসেজের ফরম্যাটের উপর নির্ভরশীল — ফরম্যাট সামান্য বদলালেই সার্চ ভেঙে যায়। স্ট্রাকচার্ড লগে প্রতিটি তথ্য একটি নির্দিষ্ট ফিল্ড (event, user_id) হিসেবে থাকে, তাই সরাসরি ফিল্ড দিয়ে কোয়েরি করা যায় (একটি ডেটাবেস কোয়েরির মতো) — টেক্সট প্যাটার্নের উপর নির্ভরতা ছাড়াই, এবং অ্যাগ্রিগেশন (যেমন "গত ১ ঘণ্টায় user_id ভিত্তিতে গ্রুপ করে লগইন-ব্যর্থতার কাউন্ট") অনেক সহজে করা যায়।

প্র ০৩ শুধু মেট্রিক্স/ড্যাশবোর্ড থাকলেই কেন যথেষ্ট নয় — লগ ও ট্রেস আলাদাভাবে দরকার কেন?

মেট্রিক্স সবসময় অ্যাগ্রিগেটেড/সারাংশ আকারে থাকে (যেমন "p99 লেটেন্সি ৮০০ms")— এটি বলে দেবে যে একটি সমস্যা আছে, কিন্তু কোন নির্দিষ্ট রিকোয়েস্ট, কোন ইউজার, কোন ইনপুট এই সমস্যা তৈরি করছে তা বলবে না। সেই নির্দিষ্ট ঘটনার বিস্তারিত পেতে লগ দরকার, আর যদি সমস্যাটি একাধিক সার্ভিস জুড়ে লেটেন্সি সম্পর্কিত হয় তাহলে ঠিক কোন সার্ভিসে সময় ব্যয় হচ্ছে তা বুঝতে ট্রেস দরকার। তিনটি একসাথে একটি সম্পূর্ণ "কী হচ্ছে, কেন হচ্ছে, কোথায় হচ্ছে" ছবি দেয় — কোনো একটি একা পূর্ণাঙ্গ ডিবাগিং সক্ষমতা দেয় না।

অনুশীলন

  1. চিন্তা করুন: একটি ই-কমার্স চেকআউট ফ্লো-তে (কার্ট → পেমেন্ট → ইনভেন্টরি → কনফার্মেশন) কোন কোন জায়গায় লগ, মেট্রিক্স, ও ট্রেস আলাদাভাবে দরকারি হবে তা চিহ্নিত করুন।

    লগ: প্রতিটি পেমেন্ট ব্যর্থতার নির্দিষ্ট কারণ (কার্ড ডিক্লাইন, টাইমআউট ইত্যাদি)। মেট্রিক্স: সামগ্রিক চেকআউট সাফল্যের হার, গড়/p99 চেকআউট লেটেন্সি, ঘণ্টাপ্রতি অর্ডার কাউন্ট — এসব ড্যাশবোর্ডে দেখে বোঝা যায় সামগ্রিকভাবে সিস্টেম সুস্থ কি না। ট্রেস: একটি নির্দিষ্ট ধীরগতির চেকআউট রিকোয়েস্ট তদন্ত করতে — পেমেন্ট সার্ভিসে সময় লাগছে নাকি ইনভেন্টরি সার্ভিসে, সেটা বুঝতে পুরো কল-চেইনের span দরকার।

  2. কোড পরিবর্তন করুন: উপরের কোড সেলে call_service_a-এর ভেতরে call_service_b()-এর পাশাপাশি আরেকটি নতুন call_service_c() ফাংশন (একই প্যাটার্নে, depth ২) যোগ করুন এবং দেখুন ট্রেস আউটপুটে এটি কীভাবে দেখা যায়।

    call_service_c()-ও call_service_b()-এর মতো depth ২-তে (handle_request-এর ভেতরে call_service_a-এর ভেতরে) একটি নতুন span যোগ করবে, কিন্তু call_service_b-এর পরে তার নিজের start/end টাইমস্ট্যাম্প নিয়ে। যেহেতু sorting শুরুর সময় অনুযায়ী হয়, নতুন span call_service_b-এর ঠিক পরে দেখাবে, একই ইনডেন্টেশন লেভেলে (sibling span, একে অপরের ভেতরে নয়) — এটি দেখায় একই প্যারেন্টের নিচে একাধিক sequential span কীভাবে ট্রেসে উপস্থাপিত হয়।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

পূর্ববর্তী পাঠ
L40 · এনক্রিপশন ও ডেটা সুরক্ষা