ট্র্যাক
/
Go
Go
/
অনুশীলনী
/
লগ ফাইল পার্সিং
লগ ফাইল পার্সিং

লগ ফাইল পার্সিং

লার্নিং অনুশীলনী

ভূমিকা

regexp প্যাকেজটি Go-তে রেগুলার এক্সপ্রেশনের সাপোর্ট দেয়।

সিনট্যাক্স

যে রেগুলার এক্সপ্রেশনগুলো গ্রহণ করা হয়, তাদের সিনট্যাক্স হলো Perl, Python আর অন্যান্য ভাষায় ব্যবহৃত একই সাধারণ সিনট্যাক্স।

সার্চ প্যাটার্ন আর ইনপুট টেক্সট দুটোই UTF-8 হিসেবে ব্যাখ্যা করা হয়।

স্ট্রিং তৈরিতে ব্যাকটিক (`) ব্যবহার করলে ব্যাকস্ল্যাশ (\) কোনো বিশেষ অর্থ বহন করে না এবং ট্যাব \t বা নিউলাইন \n-এর মতো বিশেষ ক্যারেক্টারের শুরুও চিহ্নিত করে না:

"\t\n" // regular string literal with 2 characters: a tab and a newline
`\t\n`// raw string literal with 4 characters: two backslashes, a 't', and an 'n'

এ কারণে রেগুলার এক্সপ্রেশন তৈরিতে ব্যাকটিক ব্যবহার করাই ভালো, কারণ তাহলে ব্যাকস্ল্যাশ এস্কেপ করতে হয় না:

"\\" // string with a single backslash
`\\` // string with 2 backslashes

প্যাটার্ন কম্পাইল করা - RegExp টাইপ

একটি রেগুলার এক্সপ্রেশন ব্যবহার করতে হলে প্রথমে স্ট্রিং প্যাটার্নটি কম্পাইল করতে হয়। এখানে কম্পাইল বলতে রেগুলার এক্সপ্রেশনের স্ট্রিং প্যাটার্নটি নিয়ে সেটিকে এমন একটি অভ্যন্তরীণ রূপে রূপান্তর করাকে বোঝায়, যা নিয়ে কাজ করা আরও সহজ। প্রতিটি প্যাটার্ন আমাদের একবারই কম্পাইল করতে হয়, এরপর কম্পাইল করা রেগুলার এক্সপ্রেশনটি আমরা বহুবার ব্যবহার করতে পারি। regexp.Regexp টাইপটি একটি কম্পাইল করা রেগুলার এক্সপ্রেশনকে প্রকাশ করে। regexp.Compile ফাংশনটি দিয়ে আমরা একটি স্ট্রিং প্যাটার্নকে regexp.Regexp টাইপে কম্পাইল করতে পারি। কম্পাইল করতে ব্যর্থ হলে এই ফাংশনটি nil আর একটি এরর রিটার্ন করে:

re, err := regexp.Compile(`(a|b)+`)
fmt.Println(re, err) // => (a|b)+ <nil>
re, err = regexp.Compile(`a|b)+`)
fmt.Println(re, err) // => <nil> error parsing regexp: unexpected ): `a|b)+`

Compile-এর সুবিধাজনক বিকল্প হলো MustCompile ফাংশনটি:

re = regexp.MustCompile(`[a-z]+\d*`)

এই ফাংশনটি ব্যবহার করলে এরর হ্যান্ডেল করার দরকার হয় না।

Caution

MustCompile শুধু তখনই ব্যবহার করা উচিত যখন আমরা নিশ্চিতভাবে জানি যে প্যাটার্নটি কম্পাইল হবে, কারণ অন্যথায় প্রোগ্রামটি প্যানিক করবে।

রেগুলার এক্সপ্রেশনের মেথড

Regexp-এর ১৬টি মেথড আছে যেগুলো একটি রেগুলার এক্সপ্রেশন ম্যাচ করে এবং ম্যাচ হওয়া টেক্সট শনাক্ত করে। এদের নাম এই রেগুলার এক্সপ্রেশন দিয়ে মেলানো যায়:

Find(All)?(String)?(Submatch)?(Index)?
  • All থাকলে মেথডটি পুরো এক্সপ্রেশনের ধারাবাহিক, ওভারল্যাপ-বিহীন ম্যাচগুলো খুঁজে বের করে।
  • String থাকলে আর্গুমেন্টটি একটি স্ট্রিং; নাহলে এটি বাইটের একটি স্লাইস; রিটার্ন ভ্যালুগুলো উপযুক্তভাবে সমন্বয় করা হয়।
  • Submatch থাকলে রিটার্ন ভ্যালুটি একটি স্লাইস, যা এক্সপ্রেশনের ধারাবাহিক সাবম্যাচগুলো শনাক্ত করে।
  • Index থাকলে ইনপুট স্ট্রিংয়ের ভেতরে বাইট ইনডেক্স জোড়া দিয়ে ম্যাচ আর সাবম্যাচ শনাক্ত করা হয়।

আরও কিছু মেথড আছে:

  • রেগুলার এক্সপ্রেশনের ম্যাচগুলোকে রিপ্লেসমেন্ট স্ট্রিং দিয়ে বদলে দেওয়া, এবং
  • রেগুলার এক্সপ্রেশন দিয়ে আলাদা করা স্ট্রিংগুলো ভাগ করা।

সব মিলিয়ে regexp প্যাকেজটি ৪০টিরও বেশি ফাংশন আর মেথড সংজ্ঞায়িত করে। নিচে আমরা কয়েকটি মেথডের ব্যবহার দেখব। এই আর অন্যান্য ফাংশনের বিস্তারিত জানতে API ডকুমেন্টেশন দেখুন।

MatchString-এর উদাহরণ

MatchString মেথডটি জানায় যে একটি স্ট্রিংয়ে রেগুলার এক্সপ্রেশনের কোনো ম্যাচ আছে কি না।

re = regexp.MustCompile(`[a-z]+\d*`)
b = re.MatchString("[a12]")       // => true
b = re.MatchString("12abc34(ef)") // => true
b = re.MatchString(" abc!")       // => true
b = re.MatchString("123 456")     // => false

FindString-এর উদাহরণ

FindString মেথডটি এমন একটি স্ট্রিং রিটার্ন করে যেখানে রেগুলার এক্সপ্রেশনের একেবারে বাম দিকের ম্যাচের টেক্সট থাকে।

re = regexp.MustCompile(`[a-z]+\d*`)
s = re.FindString("[a12]")       // => "a12"
s = re.FindString("12abc34(ef)") // => "abc34"
s = re.FindString(" abc!")       // => "abc"
s = re.FindString("123 456")     // => ""

FindStringSubmatch-এর উদাহরণ

FindStringSubmatch মেথডটি স্ট্রিংয়ের একটি স্লাইস রিটার্ন করে, যেখানে রেগুলার এক্সপ্রেশনের একেবারে বাম দিকের ম্যাচের টেক্সট আর তার সাব-এক্সপ্রেশনগুলোর ম্যাচ, যদি থাকে, থাকে। ক্যাপচারিং গ্রুপের সাথে মেলে এমন স্ট্রিং শনাক্ত করতে এটি ব্যবহার করা যায়। রিটার্ন ভ্যালু nil হলে বোঝা যায় কোনো ম্যাচ নেই।

re = regexp.MustCompile(`[a-z]+(\d*)`)
sl = re.FindStringSubmatch("[a12]")       // => []string{"a12","12"}
sl = re.FindStringSubmatch("12abc34(ef)") // => []string{"abc34","34"}
sl = re.FindStringSubmatch(" abc!")       // => []string{"abc",""}
sl = re.FindStringSubmatch("123 456")     // => <nil>

ReplaceAllString-এর উদাহরণ

re.ReplaceAllString(src,repl) মেথডটি src-এর একটি কপি রিটার্ন করে, যেখানে re রেগুলার এক্সপ্রেশনের ম্যাচগুলো repl রিপ্লেসমেন্ট স্ট্রিং দিয়ে বদলে দেওয়া হয়।

re = regexp.MustCompile(`[a-z]+\d*`)
s = re.ReplaceAllString("[a12]", "X")       // => "[X]"
s = re.ReplaceAllString("12abc34(ef)", "X") // => "12X(X)"
s = re.ReplaceAllString(" abc!", "X")       // => " X!"
s = re.ReplaceAllString("123 456", "X")     // => "123 456"

Split-এর উদাহরণ

re.Split(s,n) মেথডটি s টেক্সটকে এক্সপ্রেশন দিয়ে আলাদা করা সাবস্ট্রিংয়ে ভাগ করে এবং সেই এক্সপ্রেশন ম্যাচগুলোর মাঝের সাবস্ট্রিংগুলোর একটি স্লাইস রিটার্ন করে। n সংখ্যাটি রিটার্ন করার সর্বোচ্চ সাবস্ট্রিং সংখ্যা নির্ধারণ করে। n<0 হলে মেথডটি সব সাবস্ট্রিং রিটার্ন করে।

re = regexp.MustCompile(`[a-z]+\d*`)
sl = re.Split("[a12]", -1)      // => []string{"[","]"}
sl = re.Split("12abc34(ef)", 2) // => []string{"12","(ef)"}
sl = re.Split(" abc!", -1)      // => []string{" ","!"}
sl = re.Split("123 456", -1)    // => []string{"123 456"}

নির্দেশনা

এই অনুশীলনীতে লগ ফাইল পার্সিং নিয়ে আলোচনা করা হয়েছে।

সাম্প্রতিক একটি নিরাপত্তা পর্যালোচনার পর আপনাকে সংগঠনের আর্কাইভ করা লগ ফাইল পরিষ্কার করার দায়িত্ব দেওয়া হয়েছে।

ফাংশনগুলোতে পাঠানো সব স্ট্রিং নন-নাল এবং শুরু ও শেষে কোনো স্পেস ছাড়া হবে বলে নিশ্চিত করা হয়েছে।

1. বিকৃত লগ লাইন শনাক্ত করুন

আপনার আর্কাইভে কতগুলো লগ লাইন বর্তমান মানদণ্ড মানে না, সে সম্পর্কে আপনার কিছুটা ধারণা দরকার। আপনার বিশ্বাস, একটি সহজ পরীক্ষাই বলে দিতে পারে কোনো লগ লাইন বৈধ কি না। বৈধ হিসেবে গণ্য হতে একটি লাইনের শুরুতে নিচের স্ট্রিংগুলোর একটি থাকতে হবে:

  • [TRC]
  • [DBG]
  • [INF]
  • [WRN]
  • [ERR]
  • [FTL]

IsValidLine ফাংশনটি বাস্তবায়ন করুন, যাতে কোনো স্ট্রিং বৈধ না হলে false এবং অন্যথায় true রিটার্ন করে।

IsValidLine("[ERR] A good error here")
// => true
IsValidLine("Any old [ERR] text")
// => false
IsValidLine("[BOB] Any old text")
// => false

2. লগ লাইনটি ভাগ করুন

সংগঠনে একটি নতুন দল যোগ দিয়েছে, আর আপনি দেখলেন তাদের লগ ফাইলগুলো "ফিল্ড" আলাদা করতে অদ্ভুত একটি বিভাজক ব্যবহার করছে। কোলন ":" -এর মতো বোধগম্য কোনো কিছুর বদলে তারা "<--->" বা "<=>" -এর মতো স্ট্রিং ব্যবহার করে (কারণ এটি দেখতে বেশি সুন্দর), আসলে যেকোনো স্ট্রিং, যার প্রথম অক্ষর "<", শেষ অক্ষর ">" এবং মাঝে "~", "*", "=" আর "-" অক্ষরগুলোর যেকোনো সমন্বয় থাকতে পারে।

SplitLogLine ফাংশনটি বাস্তবায়ন করুন, যা একটি লাইন নেয় এবং এমন একটি স্ট্রিং-এর অ্যারে রিটার্ন করে যার প্রতিটিতে একটি ফিল্ড থাকে।

SplitLogLine("section 1<*>section 2<~~~>section 3")
// => []string{"section 1", "section 2", "section 3"},

3. উদ্ধৃতি চিহ্নের ভেতরে password থাকা লাইনের সংখ্যা গণনা করুন

দলটির উদ্ধৃতি চিহ্নের ভেতরে পাসওয়ার্ডের উল্লেখ সম্পর্কে জানা দরকার, যাতে সেগুলো হাতে-কলমে যাচাই করা যায়।

হাতে যাচাইয়ের কাজটি কত বড় হতে পারে তার একটি আভাস দিতে CountQuotedPasswords ফাংশনটি বাস্তবায়ন করুন।

এমন লগ লাইন শনাক্ত করুন যেখানে "password" স্ট্রিংটি উদ্ধৃতি চিহ্ন দিয়ে ঘেরা, এবং যা বড় হাতের বা ছোট হাতের অক্ষরের যেকোনো সমন্বয়ে থাকতে পারে। "password" -এর আগে ও পরে উদ্ধৃতি চিহ্নের মাঝে বাড়তি কনটেন্ট থাকার সম্ভাবনাও হিসাবে ধরতে হবে। প্রতিটি লাইনে সর্বোচ্চ দুটি উদ্ধৃতি চিহ্ন থাকবে।

রুটিনে পাঠানো লাইনগুলো টাস্ক ১-এ বর্ণিত নিয়মে বৈধ হতে পারে, আবার নাও হতে পারে। বৈধ হোক বা না হোক, আমরা সেগুলো একইভাবে প্রক্রিয়া করি।

lines := []string{
    `[INF] passWord`, // contains 'password' but not surrounded by quotation marks
    `"passWord"`,  // count this one
    `[INF] User saw error message "Unexpected Error" on page load.`, // does not contain 'password'
    `[INF] The message "Please reset your password" was ignored by the user`, // count this one
}
// => 2

4. লগ থেকে আর্টিফ্যাক্ট সরান

আপনি দেখতে পেয়েছেন, লগগুলোর কিছু আপস্ট্রিম প্রসেসিং "end-of-line" লেখাটি তার পরে একটি লাইন নম্বরসহ (মাঝে কোনো স্পেস ছাড়া) সারা লগে ছড়িয়ে দিয়েছে।

RemoveEndOfLineText ফাংশনটি বাস্তবায়ন করুন, যা একটি স্ট্রিং নেবে, end-of-line লেখাটি সরিয়ে ফেলবে এবং একটি "পরিষ্কার" স্ট্রিং রিটার্ন করবে।

যে লাইনগুলোতে end-of-line লেখা নেই, সেগুলো অপরিবর্তিত অবস্থায় রিটার্ন করতে হবে।

শুধু end of line স্ট্রিংটি সরিয়ে ফেলুন। হোয়াইটস্পেস ঠিক করার চেষ্টা করবেন না।

RemoveEndOfLineText("[INF] end-of-line23033 Network Failure end-of-line27")
// => "[INF]  Network Failure "

5. ব্যবহারকারীর নাম দিয়ে লাইন ট্যাগ করুন

আপনি লক্ষ্য করেছেন, কিছু লগ লাইনে এমন বাক্য আছে যা ব্যবহারকারীদের কথা বলে। এই বাক্যগুলোতে সবসময় "User" স্ট্রিংটি থাকে, তারপর এক বা একাধিক স্পেস অক্ষর, তারপর একটি ব্যবহারকারীর নাম। আপনি এমন লাইন ট্যাগ করার সিদ্ধান্ত নেন।

TagWithUserName ফাংশনটি বাস্তবায়ন করুন, যা লগ লাইন প্রক্রিয়া করে:

  • যে লাইনগুলোতে "User " স্ট্রিংটি নেই, সেগুলো অপরিবর্তিত থাকে।
  • যে লাইনগুলোতে "User " স্ট্রিংটি আছে, সেই লাইনের শুরুতে [USR] এবং তারপর ব্যবহারকারীর নাম বসান।

উদাহরণস্বরূপ:

result := TagWithUserName([]string{
    "[WRN] User James123 has exceeded storage space.",
	"[WRN] Host down. User   Michelle4 lost connection.",
	"[INF] Users can login again after 23:00.",
	"[DBG] We need to check that user names are at least 6 chars long.",
})
// => []string {
//  "[USR] James123 [WRN] User James123 has exceeded storage space.",
//  "[USR] Michelle4 [WRN] Host down. User   Michelle4 lost connection.",
//  "[INF] Users can login again after 23:00.",
//  "[DBG] We need to check that user names are at least 6 chars long."
// }

আপনি ধরে নিতে পারেন যে:

  • লগে ব্যবহারকারীর নামের পরে অন্তত একটি হোয়াইটস্পেস অক্ষর থাকে।
  • প্রতিটি লাইনে "User " স্ট্রিংটি সর্বোচ্চ একবার থাকে।
  • ব্যবহারকারীর নাম খালি নয় এমন স্ট্রিং, যেখানে কোনো হোয়াইটস্পেস থাকে না।
GitHub-এর মাধ্যমে সম্পাদনা করুন লিংকটি একটি নতুন উইন্ডো বা ট্যাবে খোলে
Go Exercism

লগ ফাইল পার্সিং শুরু করতে প্রস্তুত?

Exercism-এ সাইন আপ করুন, Go ট্র্যাকের 34টি কনসেপ্ট165টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।