لینک‌های مفید برای شرکت در مسابقه:

در طول مسابقه، می‌توانید سؤالات خود را از قسمت «سؤال بپرسید» مطرح کنید.

پارسر لاگ Nginx


Nginx

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

جزئیات پروژه🔗

پروژه‌ی اولیه را از این لینک دانلود کنید. ساختار فایل‌های پروژه به‌صورت زیر است:

nginx-log-parser
├── exceptions
│   ├── LineDoesNotMatchException.java
│   ├── PropertyNotFoundException.java
│   └── TransformerNotFoundException.java
├── Condition.java
├── Log.java
├── LogList.java
├── LogParser.java
├── LogParserSampleTest.java
└── LogPropertyTransformerRepository.java
Plain text

بسته‌ی exceptions🔗

این بسته شامل استثناهای موردنیاز برنامه است. همه‌ی کلاس‌های موجود در این بسته از کلاس RuntimeException ارث‌بری می‌کنند.

کلاس Log🔗

هر نمونه از این کلاس بیانگر لاگ یک درخواست است. هر لاگ شامل یک یا چند ویژگی است. این کلاس از قبل به‌طور کامل پیاده‌سازی شده و شامل متدهای زیر است:

  • متد setProperty(String name, String value): این متد با دریافت نام و مقدار یک ویژگی، آن ویژگی را به ویژگی‌های لاگ اضافه می‌کند.
  • متد getProperty(String name): این متد با دریافت نام یک ویژگی، مقدار آن ویژگی را برمی‌گرداند. در صورتی که ویژگی‌ای با نام واردشده موجود نباشد، یک استثنا از نوع PropertyNotFoundException پرتاب می‌شود.

کلاس LogPropertyTransformerRepository🔗

بعضی از ویژگی‌های لاگ‌ها (مانند کد وضعیت پاسخ و زمان ارسال درخواست) ماهیت عددی دارند. این کلاس، مجموعه‌ای از Function<String, Long> را در خود نگه‌داری می‌کند تا با استفاده از آن‌ها، بتوان مقدار عددی معادل بعضی از ویژگی‌های لاگ‌ها را دریافت کرد. این کلاس از قبل به‌طور کامل پیاده‌سازی شده و شامل متدهای زیر است:

  • متد addTransformer(String propertyName, Function<String, Long> transformer): این متد با دریافت نام یک ویژگی و یک تابع برای تبدیل مقدار رشته‌ای ویژگی به مقدار عددی، آن تابع را در Map موجود در کلاس ذخیره می‌کند.
  • متد getTransformer(String propertyName): این متد با دریافت نام یک ویژگی، تابع مبدل مقدار آن ویژگی به مقدار عددی را برمی‌گرداند. در صورتی که تابعی برای ویژگی موردنظر تعریف نشده باشد، یک استثنا از نوع TransformerNotFoundException پرتاب می‌شود.

کلاس Condition🔗

هر نمونه از این کلاس، بیانگر یک شرط (ساده یا مرکب) برای فیلتر کردن لاگ‌ها است. پراپرتی‌های موردنیاز خود را در این کلاس تعریف کرده و متدهای زیر را در آن پیاده‌سازی کنید:

  • متد استاتیک equal(String propertyName, String value) را طوری پیاده‌سازی کنید که با دریافت نام و مقدار یک ویژگی، یک نمونه از کلاس Condition برگرداند که اطلاعات مربوط به شرط معادل بودن ویژگی با مقدار موردنظر در آن موجود باشد.
  • متد استاتیک notEqual(String propertyName, String value) را طوری پیاده‌سازی کنید که با دریافت نام و مقدار یک ویژگی، یک نمونه از کلاس Condition برگرداند که اطلاعات مربوط به شرط معادل نبودن ویژگی با مقدار موردنظر در آن موجود باشد.
  • متد استاتیک in(String propertyName, String... values) را طوری پیاده‌سازی کنید که با دریافت نام یک ویژگی و تعدادی مقدار، یک نمونه از کلاس Condition برگرداند که اطلاعات مربوط به شرط معادل بودن ویژگی با حداقل یکی از مقادیر واردشده در آن موجود باشد.
  • متد استاتیک in(String propertyName, String... values) را طوری پیاده‌سازی کنید که با دریافت نام یک ویژگی و تعدادی مقدار، یک نمونه از کلاس Condition برگرداند که اطلاعات مربوط به شرط معادل نبودن ویژگی با هیچ یک از مقادیر واردشده در آن موجود باشد.
  • متد استاتیک lessThan(String propertyName, Long value) را طوری پیاده‌سازی کنید که با دریافت نام یک ویژگی و یک عدد، یک نمونه از کلاس Condition برگرداند که اطلاعات مربوط به شرط کوچک‌تر بودن مقدار عددی ویژگی از عدد واردشده در آن موجود باشد. برای پیاده‌سازی این متد، از کلاس LogPropertyTransformerRepository استفاده کنید.
  • متد استاتیک lessOrEqual(String propertyName, Long value) را طوری پیاده‌سازی کنید که با دریافت نام یک ویژگی و یک عدد، یک نمونه از کلاس Condition برگرداند که اطلاعات مربوط به شرط کوچک‌تر یا مساوی بودن مقدار عددی ویژگی از عدد واردشده در آن موجود باشد. برای پیاده‌سازی این متد، از کلاس LogPropertyTransformerRepository استفاده کنید.
  • متد استاتیک greaterThan(String propertyName, Long value) را طوری پیاده‌سازی کنید که با دریافت نام یک ویژگی و یک عدد، یک نمونه از کلاس Condition برگرداند که اطلاعات مربوط به شرط بزرگ‌تر بودن مقدار عددی ویژگی از عدد واردشده در آن موجود باشد. برای پیاده‌سازی این متد، از کلاس LogPropertyTransformerRepository استفاده کنید.
  • متد استاتیک greaterOrEqual(String propertyName, Long value) را طوری پیاده‌سازی کنید که با دریافت نام یک ویژگی و یک عدد، یک نمونه از کلاس Condition برگرداند که اطلاعات مربوط به شرط بزرگ‌تر یا مساوی بودن مقدار عددی ویژگی از عدد واردشده در آن موجود باشد. برای پیاده‌سازی این متد، از کلاس LogPropertyTransformerRepository استفاده کنید.
  • متد and(Condition condition) را طوری پیاده‌سازی کنید که با دریافت یک شرط، یک نمونه از کلاس Condition برگرداند که شرط آن برابر با AND منطقی شرط فعلی و شرط واردشده باشد.
  • متد or(Condition condition) را طوری پیاده‌سازی کنید که با دریافت یک شرط، یک نمونه از کلاس Condition برگرداند که شرط آن برابر با OR منطقی شرط فعلی و شرط واردشده باشد.
  • متد استاتیک not(Condition condition) را طوری پیاده‌سازی کنید که با دریافت یک شرط، یک نمونه از کلاس Condition برگرداند که شرط آن برابر با NOT منطقی شرط فعلی و شرط واردشده باشد.
  • متد test(Log log) را طوری پیاده‌سازی کنید که با دریافت یک لاگ، در صورتی که لاگ در شرایط فعلی صدق کند، مقدار true و در غیر این‌صورت، مقدار false را برگرداند.

نکته: متدهای and، or و not را باید به‌گونه‌ای پیاده‌سازی کنید که نمونه‌های جدیدی از کلاس Condition ایجاد شوند. در واقع، نمونه‌های کلاس Condition باید immutable باشند.

کلاس LogList🔗

این کلاس، یک List از لاگ‌ها را در خود نگه‌داری می‌کند.

  • متد all این کلاس را طوری پیاده‌سازی کنید که یک LogList شامل یک List جدید که لاگ‌های موجود در آبجکت فعلی در آن قرار گرفته‌اند ایجاد کرده و برگرداند.
  • متد where این کلاس را طوری پیاده‌سازی کنید که با دریافت یک Condition، یک LogList شامل یک List جدید که لاگ‌های موجود در آبجکت فعلی در آن قرار گرفته‌اند و در شرط واردشده صدق می‌کنند ایجاد کرده و برگرداند. ترتیب لاگ‌های موجود در لیست را نباید تغییر دهید.

کلاس LogParser🔗

وظیفه‌ی این کلاس، پارس کردن رشته‌ی لاگ‌ها است. لاگ‌ها یک schema مشخص دارند که در قالب یک رشته به کانستراکتور این کلاس داده می‌شود. schema شامل تعدادی متغیر نیز هست که با $ آغاز می‌شوند. مثالی از یک schema:

$remote_addr - $remote_user [$time_local] "$request" $status $bytes_sent "$http_referer" "$http_user_agent"
Plain text

در این‌صورت، لاگ‌های پارس‌شده باید شامل ویژگی‌های remote_addr، remote_user و... باشند.

نمونه‌ای از یک لاگ که طبق فرمت schema بالا است:

127.0.0.1 - - [07/Jul/2018:17:37:28 +0200] "GET /7d32ce87648a4050faca.hot-update.json HTTP/1.1" 200 43 "http://test.local/" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0"
Plain text
  • متد parseLog(String line) این کلاس را طوری پیاده‌سازی کنید که با دریافت یک رشته‌ی یک‌خطی، یک Log متناظر با لاگ واردشده برگرداند. در صورتی که لاگ واردشده با schema داده‌شده مطابقت نداشت، یک استثنا از نوع LineDoesNotMatchException پرتاب کنید. ترتیب ویژگی‌های Log خروجی باید مطابق ترتیب متغیرهای موجود در schema باشد.
  • متد parseLogs(String logs) را طوری پیاده‌سازی کنید که با دریافت یک رشته‌ی یک یا چند خطی، یک LogList متناظر با لاگ‌های واردشده برگرداند. برای پیاده‌سازی این متد، از متد parseLog(String line) استفاده کنید.
  • متد parseLogs(File file) را طوری پیاده‌سازی کنید که با دریافت یک فایل، یک LogList متناظر با لاگ‌های موجود در آن برگرداند. برای پیاده‌سازی این متد، از متد parseLogs(String logs) استفاده کنید.

مثال🔗

با اجرای متد main موجود در کلاس LogParser:

public static void main(String[] args) {
    LogPropertyTransformerRepository.addTransformer("status", Long::parseLong);
    String schema = "$remote_addr - $remote_user [$time_local] \"$request\" $status $bytes_sent \"$http_referer\" \"$http_user_agent\"";
    LogParser parser = new LogParser(schema);
    LogList logs = parser.parseLogs("127.0.0.1 - - [07/Jul/2018:17:37:28 +0200] \"GET /7d32ce87648a4050faca.hot-update.json HTTP/1.1\" 200 43 \"http://test.local/\" \"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0\"");
    System.out.println(logs.where(Condition.greaterOrEqual("status", 200L)));
}
Java

خروجی باید به‌صورت زیر باشد:

[Log[remote_addr=127.0.0.1, remote_user=-, time_local=07/Jul/2018:17:37:28 +0200, request=GET /7d32ce87648a4050faca.hot-update.json HTTP/1.1, status=200, bytes_sent=43, http_referer=http://test.local/, http_user_agent=Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0]]
Plain text

نکته: تست‌های نمونه‌ی سؤال در کلاس LogParserSampleTest موجود هستند. با افزودن JUnit به classpath پروژه، می‌توانید آن‌ها را اجرا کنید.

آن‌چه باید آپلود کنید🔗

پس از پیاده‌سازی موارد خواسته‌شده، یک فایل زیپ آپلود کنید که وقتی آن را باز می‌کنیم، با فایل‌های زیر مواجه شویم:

Condition.java
LogList.java
LogParser.java
Plain text
ارسال پاسخ برای این سؤال
در حال حاضر شما دسترسی ندارید.