اجرای پروسه و آنالیز لاگهای خروجی با جاوا و کاتلین

داستان از اونجایی شروع شد که نیاز داشتم برای برنامه های جاوا (مثلا مدیریت ترنسکدر vod و catchup و...) که روی سرورهای استریم لینوکسیمون ران میکنیم، بتونم لاگ خروجی رو آنالیز کنم و یسری اطلاعات ازشون بیرون بکشم.بصورت کلی این مقاله به این مورد اشاره میکنه که چجوری از برنامه ی جاوا، پروسه ی جدیدی ران کنیم و بتونیم به لاگش دسترسی داشته باشیم.سطح این آموزش یکمی نسبت به آموزشهای قبلی که توی ویرگول منتشر کردم بالاتره و برای درک بهترش باید با برنامه نویسی (ترجیحا جاوا و کاتلین) و مفاهیمی مثل پروسس و کامند آشنا باشیم (دونستن مفهوم نخ و چند نخی (Thread و MultiThreading) مزیت خیلی بزرگی محسوب میشه)یه راه برای اجرای یه کامند از برنامه ی جاواتون اجرای خط زیر هست:Process process = Runtime.getRuntime().exec(&quotYour Command&quot)خب با اجرای این خط کامندمون رو اجرا میکنیم و خروجی یه پروسس هست. این پروسس به چه دردی میخوره؟ یکی از کاربردهاش اینه که اگه دستور زیر رو بکار ببریم:proccess.waitFor()برنامه (در حقیقت بهتره بگیم نخ (یا همون Thread)ی که در حال اجرای این دستوره) منتظر اجرای این پروسس میمونه. عملا خطوطی که قبل از نوشتن این خط دوم بکار بردیم کاری به proccess ندارن و راه خودشون رو ادامه میدن تا زمانی که برسیم به این خط و منتظر میمونیم. توضیحات بیشتر این تابع بصورت زیره:* Causes the current thread to wait, if necessary, until the* process represented by this {@code Process} object has* terminated. This method returns immediately if the process* has already terminated. If the process has not yet* terminated, the calling thread will be blocked until the* process exits. راه دومی که میشه یه کامند رو اجرا کنیم بصورت زیر هست:Process process = ProcessBuilder(&quot/bin/sh&quot, &quot-c&quot, YourCommand).start()اینکه این دو راه چه فرقایی دارن یا اینکه چرا ساختار دستور دوم رو به این صورت نوشتیم رو فعلا کاری نداریم چون این مطلب تمرکزش روی بررسی لاگ های خروجیه.خب تا اینجا پروسس رو ران کردیم ولی برای اینکه بتونیم لاگ های پروسسمون رو حین اجرای برنامه ی خودمون هم ببنیم (چون توی حالت عادی این اتفاق نمیفته، بدلیل اینکه ما یه پروسس جدید اجرا کردیم که خودش برای خودش جدا داره لاگ میندازه) میتونیم بصورت زیر عمل کنیم (کاتلین): Process process = ProcessBuilder(&quot/bin/sh&quot, &quot-c&quot, YourCommand).start() val out = PipeStream(proc.inputStream, System.out) val err = PipeStream(proc.errorStream, System.err) out.start() err.start() val exitValue = proc.waitFor()و نکته ی مهم کلاس PipeStream هست که یه Thread هست و بصورت زیر پیاده سازیش میکنیم:internal class PipeStream(var `is`: InputStream, var ps: PrintStream) : Thread() { override fun run() { try { val sc = Scanner(`is`) while (sc.hasNextLine()) { var line = sc.nextLine().toLowerCase() ps.println(line) } } catch (e: IOException) { ... } } }یذره ببینیم چیکار کردیم. همونطور که فهمیدیم با اجرای دستورProcess process = ProcessBuilder(&quot/bin/sh&quot, &quot-c&quot, YourCommand).start()کامندمون رو تحت عنوان یه پروسه ی جدید اجرا کردیم. توی خط val out = PipeStream(proc.inputStream, System.out)یه شی از کلاس PipeStream تعریف کردیم. همونطور که طبق خط زیر موقع تعریف کلاسمون دیدیمinternal class PipeStream(var `is`: InputStream, var ps: PrintStream)دو تا ورودی نیاز داره (برای اینکه توی مثال ما بتونیه لاگ های موجود روی یه استریم رو بندازه روی یه استریم دیگه).برای ورودی اول InputStream (یا همونی که میخوایم لاگ هارو از روش بخونیم) proc.inputStream رو پاس دادیم: یعنی لاگ عادی پروسه ای که اجرا کردیمو به عنوان PrintStream (یا همونی که میخوایم لاگ ها رو روش بنویسیم) System.out رو پاس دادیم: نشون دهنده ی همون خروجی برنامه هست (میتونیم با دستور System.out.println خطی رو چاپ کنیم)خب پس ما یه پروسه ایجاد کردیم، لاگ اون رو میگیریم و میدیم به کلاس PipeStream که یه نخ هست و داخل تابع run اومدیم بصورت زیر، خط به خط استریم پروسه جدیدمون رو خوندیم:val sc = Scanner(`is`) while (sc.hasNextLine()) { var line = sc.nextLine().toLowerCase()و با خط زیر، خطی رو که از لاگ پروسه ی در حال اجرامون خوندیم رو تحت عنوان لاگ برنامه ی خودمون نشون میدیم:ps.println(line)و همینطور که میتونیم اون رو نشون بدیم میتونیم هر عمل دیگه ای مثل سرچ رو هم روش انجام بدیم. یا مثلا فیلتری بذاریم که فقط یسری از لاگ ها رو نشون بدیم. بصورت کلی line نشون دهنده ی تک تک خطوط لاگ اون پروسس هست و هر کاری بخوایم میتونیم باهاش بکنیم.این توضیحات برای خط val out = PipeStream(proc.inputStream, System.out)بود. و خط زیر هم دقیقا همین عملکرد رو داره با این تفاوت که همین اعمال رو روی لاگ های خطای پروسسمون میتونه انجام بده:val err = PipeStream(proc.errorStream, System.err) فقط دقت کنیم که هردوی متغیرهای out و err که توی دو خط کد بالا تعریف کردیم از نوع thread هستن و برای اینکه بتونیم کارایی که میخوایم رو انجام بدن باید بصورت زیر اجراشون کنیم:out.start() err.start()و در نهایت هم اگه بخوایم روند بعدی کدمون با توجه به لاگ ها و خروجی های پروسه ای که ایجاد کردیم طی بشن با اجرای دستور زیر منتطر میمونیم تا کار پروسه ای که ایجاد کردیم تموم بشه:val exitValue = proc.waitFor()تابع waitFor یه عدد برمیگردونه که بصورت پیشفرض عدد صفره و معنی اینو میده که برنامه توی حالت عادی کارش تموم شده (کسی ترمینیتش نکرده یا هر مورد غیر منتظره ی دیگه ای پیش نیومده و برنامه روند عادی خودش رو طی کرده، دقت کنیم که صفر بودن خروجی یعنی برنامه کارش رو بدون مشکل تموم کرده ولی اینکه چه کارایی کرده یا نکرده یا هر مسئله ای که توی لاگ مشخص میشه رو میتونیم از توی لاگی که ازش میگیریم آنالیز کنیم).اگرهم از waitFor استفاده نکنیم اتفاقی نمیفته. در حقیقت با اجرای تابع start پروسه ران میشه و کار خودشو میکنه، ما هم میتونیم با صدا زدن waitFor منتظر اتمام اون پروسه (و یا در صورت نیاز استفاده از کد خروجی) بمونیم یا اینکه کار خودمون رو ادامه بدیم.منتشر شده در ویرگول توسط محمد قدسیان https://virgool.io/@mohammad.ghodsianhttps://virgool.io/@mohammad.ghodsian/%D8%A7%D8%AC%D8%B1%D8%A7%DB%8C-%D9%BE%D8%B1%D9%88%D8%B3%D9%87-%D9%88-%D8%A2%D9%86%D8%A7%D9%84%DB%8C%D8%B2-%D9%84%D8%A7%DA%AF%D9%87%D8%A7%DB%8C-%D8%AE%D8%B1%D9%88%D8%AC%DB%8C-%D8%A8%D8%A7-%D8%AC%D8%A7%D9%88%D8%A7-%D9%88-%DA%A9%D8%A7%D8%AA%D9%84%DB%8C%D9%86-xqzcw1tzp3n7