تكوين وقت الفاصل وطباعة سجلات بانتظام
بعد تلقي طلب ، يتم طباعة السجل بانتظام من خلال log4j. وصف المتطلبات كما يلي: يجب أن يكون السجل قادرًا على الطباعة بانتظام ، ويمكن مطابقة الفاصل الزمني. الحديث عن التوقيت ، أولاً وقبل كل شيء ، أفكر في فئة dailyrollingfileappender. توقيت مختلف. وفقًا لـ DimePattern ، يمكنك الرجوع إلى فئة SimpledAteFormat. بعض إعدادات التوقيت الشائعة هي كما يلي:
من خلال الملاحظة ، وجدت أنه لا يوجد تنسيق للتاريخ يشبه الدقائق ، لذلك كتبت فئة مخصصة تعتمد على فئة DailyRollingFileAppender. العملية كما يلي:
1) انسخ الكود المصدري لفئة dailyrollingfileappender وأعد تسميته minuToLlingAppender. من أجل تكوينه في log4j.xml ، أضف زمن عنصر التكوين وإضافة المجموعة والحصول على الطرق ؛
Private Int الفاصل الزمني = 10 ؛
2) نظرًا لأن فئة DailyRollingFilePender تستخدم فئة RollingCalendar لحساب الوقت الفاصل التالي ، ويحتاج إلى تمرير وقت المعلمة ، يتم تعديل فئة RollingCalendar كفئة داخلية ؛ نظرًا لأن طريقتها هي حساب وقت الإجراء التالي للترحيل استنادًا إلى DatePattern ، لا توجد حاجة إلى وضع زمني آخر في هذا الوقت ، طريقة التعديل هي كما يلي:
التاريخ العام getNextCheckDate (التاريخ الآن) {this.settime (الآن) ؛ this.set (Calendar.Second ، 0) ؛ this.set (calendar.millisecond ، 0) ؛ this.add (Calendar.minute ، sterealtime) ؛ العودة getTime () ؛ }3) عندما يتم مطابقة وضع الوقت وفقًا للدقائق ، يجب تعطيل وضع الوقت. قم بتغييره إلى النهائي الثابت ، وإزالة معلمات DatePattern في طريقة GET ، وضبط METLOM ومُنشئ MinuToLlingAppender في الاستجابة.
static string private datePattern = "'.'yyyy-mm-dd-hh-mm'.log'" ؛
وبالمثل ، يمكن أيضًا حذف ComputeCheckPeriod () ، الذي يخدم العديد من DatePatterns ؛ تم الانتهاء من التحول ، وفئة المنتج النهائي هي كما يلي:
حزمة net.csdn.blog ؛ استيراد java.io.file ؛ استيراد java.io.ioException ؛ استيراد java.io.InterruptedioException ؛ استيراد java.text.simpledateformat ؛ استيراد java.util.calendar ؛ استيراد java.util.date ؛ استيراد java.util.gregoriancalendar ؛ استيراد org.apache.log4j.fileappender ؛ استيراد org.apache.log4j.layout ؛ استيراد org.apache.log4j.helpers.loglog ؛ استيراد org.apache.log4j.spi.loggingevent ؛ /** * timerable by minute appender * * author coder_xia * * /public class minuterollingappender يمتد FileAppender { /** * نمط التاريخ. بشكل افتراضي ، يتم تعيين النمط على ". 'yyyyy-mm-dd" * يعني التمديد اليومي. */ private Static String DatePattern = "'.'yyyy-mm-dd-hh-mm'.log'" ؛ / *** وقت الفاصل ، الوحدة: دقائق*/ private int intertimtime = 10 ؛ /** * سيتم إعادة تسمية ملف السجل إلى قيمة متغير الجدولة * عند إدخال الفاصل الزمني التالي. على سبيل المثال ، إذا كانت فترة التمديد * ساعة واحدة ، فسيتم إعادة تسمية ملف السجل إلى قيمة * "SchedeDfilename" في بداية الساعة التالية. * * وقت الدقة عندما يحدث التمرير اعتمادًا على نشاط التسجيل. */ سلسلة خاصة ScheduleDfilename ؛ /*** في المرة القادمة التي نقدر فيها أن يحدث التمديد. */ private Long NextCheck = System.CurrentTimeMillis () - 1 ؛ التاريخ الآن = تاريخ جديد () ؛ SimpleDateFormat SDF ؛ RollingCalendar RC = New RollingCalendar () ؛ /*** لا يفعل المنشئ الافتراضي شيئًا. */public minuterollingappender () {}/** * Instantiate A <code> minuToLlingAppender </code> وافتح الملف * المصمم بواسطة <code> filename </code>. سيصبح اسم الملف المفتوح الوجهة * ouput لهذا الجيد. */ public minuterollingappender (تخطيط تخطيط ، اسم ملف السلسلة) يلقي ioException {super (التصميم ، اسم الملف ، صحيح) ؛ ActivateOptions () ؛ } / ** * regurn the intertimtime * / public int getInterValTime () {return vertaltime ؛ } / ** * param intertimtime * فترة زمنية لتعيين * / public void setInterValtime (int intertaltime) {this.Intervaltime = intertaltime ؛ } Override public void ActivateOptions () {super.activaloptions () ؛ if (filename! = null) {now.settime (System.CurrentTimeMillis ()) ؛ SDF = جديد simpleTformat (DatePattern) ؛ ملف الملف = ملف جديد (اسم الملف) ؛ ScheduleDfilename = filename + sdf.format (تاريخ جديد (file.lastmodified ())) ؛ } آخر {logLog .error ("لا يتم تعيين خيارات الملف أو DatePattern لـ Appender [" + name + "].") ؛ }} /*** التمرير الملف الحالي إلى ملف جديد. */ void Rollover () يلقي ioException {String DatedFilename = filename + sdf.format (الآن) ؛ // من السابق لأوانه التدحرج لأننا لا نزال ضمن حدود الفاصل الزمني الحالي. سيحدث التمديد بمجرد الوصول إلى الفاصل الزمني التالي. if (ScheduleDfilename.equals (DatedFilename)) {return ؛ } // أغلق الملف الحالي ، وقم بإعادة تسميته إلى DatedFilename this.closefile () ؛ ملف الملف = ملف جديد (ScheduleDfilename) ؛ if (target.exists ()) {target.delete () ؛ } ملف الملف = ملف جديد (اسم الملف) ؛ نتيجة منطقية = file.renameto (الهدف) ؛ if (result) {logLog.debug (filename + " ->" + SecrledFiLename) ؛ } آخر {logLog.Error ("فشل في إعادة تسمية [" + filename + "] إلى [" + ScheduledFilename + "].") ؛ } جرب {// هذا سيغلق الملف أيضًا. هذا جيد لأن العمليات المتعددة // الإغلاق آمنة. this.setFile (اسم الملف ، صحيح ، this.bufferedio ، this.buffersize) ؛ } catch (ioException e) {errorHandler.error ("setFile (" + filename + "، true) فشل الاتصال.") ؛ } ScheduleDfilename = DatedFilename ؛ } /*** تميز هذه الطريقة minuToLlingAppender عن فئتها الفائقة. * * <p> * قبل تسجيل الدخول فعليًا ، ستتحقق هذه الطريقة ما إذا كان الوقت قد حان للقيام * بعملة التمرير. إذا كان الأمر كذلك ، فسيقوم بجدولة وقت التمديد التالي ثم * Rollover. * */ Override subappend subappend (حدث loggingevent) {long n = system.currentTimeMillis () ؛ if (n> = nextCheck) {now.settime (n) ؛ NextCheck = rc.getNextCheckMillis (الآن) ؛ حاول {Rollover () ؛ } catch (ioException ioe) {if (ioe extulor interruptedioException) {thread.currentThRead (). interrupt () ؛ } loglog.error ("rollover () فشل." ، ioe) ؛ }} super.subappend (حدث) ؛ } /*** RollingCalendar هو فصل مساعد إلى MinuTollingAppender. بالنظر إلى نوع * الدورية والوقت الحالي ، فإنه يحسب بداية الفاصل التالي *. * */ class RollingCalendar يمتد GregorianCalendar {Private Static Final Long SerialVersionuid = -3560331770601814177L ؛ RollingCalendar () {super () ؛ } public getNextCheckMillis (Date Now) {return getNextCheckDate (الآن) .getTime () ؛ } التاريخ العام getNextCheckDate (التاريخ الآن) {this.settime (الآن) ؛ this.set (Calendar.Second ، 0) ؛ this.set (calendar.millisecond ، 0) ؛ this.add (Calendar.minute ، sterealtime) ؛ العودة getTime () ؛ }}}ملف تكوين الاختبار كما يلي:
<؟ value = "log4jtest.log"/> <param name = "append" value = "true"/> <param name = "vertaltime" value = "2"/> <layout> <param name = "conversionpattern ref = "myfile"/> </rout> </drog4j: configuration>
فيما يتعلق بتنفيذ التوقيت ، يمكنك أيضًا استخدام تطبيق المؤقت الذي توفره Java ، والذي يلغي حساب ومقارنة الوقت في كل مرة تقوم فيها بتسجيل السجل. الفرق هو في الواقع إعداد مؤشر ترابط واستدعاء طريقة التمرير. التنفيذ على النحو التالي:
حزمة net.csdn.blog ؛ استيراد java.io.file ؛ استيراد java.io.ioException ؛ استيراد java.text.simpledateformat ؛ استيراد java.util.date ؛ استيراد java.util.timer ؛ استيراد java.util.timertask ؛ استيراد org.apache.log4j.fileappender ؛ استيراد org.apache.log4j.layout ؛ استيراد org.apache.log4j.helpers.loglog ؛ الطبقة العامة TimerTaskRollingAppender يمتد FileAppender { /*** نمط التاريخ. بشكل افتراضي ، يتم تعيين النمط على ". 'yyyyy-mm-dd" * يعني التمديد اليومي. */ private Static Final String DatePattern = "'.'yyyy-mm-dd-hh-mm'.log'" ؛ / *** وقت الفاصل ، الوحدة: دقائق*/ private int intertimtime = 10 ؛ SimplEdateFormat SDF = جديد spileDateFormat (DatePattern) ؛ /*** لا يفعل المنشئ الافتراضي شيئًا. */public timertaskRollingAppender () {}/** * Instantiate A <code> timertaskRollingAppender </code> وفتح الملف * المصمم بواسطة <code> filename </code>. سيصبح اسم الملف المفتوح الوجهة * ouput لهذا الجيد. */ public timertaskRollingAppender (تخطيط تخطيط ، اسم ملف السلسلة) يلقي ioException {Super (Layout ، filenout ، true) ؛ ActivateOptions () ؛ } / ** * regurn the intertimtime * / public int getInterValTime () {return vertaltime ؛ } / ** * param intertimtime * فترة زمنية لتعيين * / public void setInterValtime (int intertaltime) {this.Intervaltime = intertaltime ؛ } Override public void ActivateOptions () {super.activaloptions () ؛ Timer Timer = New Timer () ؛ Timer.Schedule (New LogTimerTask () ، 1000 ، الفاصل الزمني * 60000) ؛ } class logtimertask يمتد timertask {Override public void run () {String datedFilename = filename + sdf.format (date ()) ؛ CloseFile () ؛ ملف ملف = ملف جديد (مستجمعات) ؛ if (target.exists ()) target.delete () ؛ ملف الملف = ملف جديد (اسم الملف) ؛ نتيجة منطقية = file.renameto (الهدف) ؛ if (النتيجة) loglog.debug (اسم الملف + " ->" + مؤرخة Filename) ؛ else loglog.error ("فشل في إعادة تسمية [" + fileName + "] إلى [" + datedfilename + "].") ؛ حاول {setFile (اسم الملف ، صحيح ، bufferedio ، bufferSize) ؛ } catch (ioException e) {errorHandler.error ("setFile (" + filename + "، true) فشل الاتصال.") ؛ }}}}}ومع ذلك ، هناك مشكلتان في التنفيذ أعلاه:
1) التزامن
مكان قد تحدث فيه مشاكل التزامن بعد استدعاء FlightFile () في Run () ، تحدث طريقة subappend () فقط لكتابة السجل. في هذه اللحظة ، يتم إغلاق الملف ، وسيتم الإبلاغ عن الخطأ التالي:
java.io.ioException: دفق مغلق في sun.nio.cs.streamencoder.ensureopen (مصدر غير معروف) في sun.nio.cs.streamencoder.write (مصدر غير معروف) في sun.nio.cs.streamencoder.write (غير معروف) في java.io.utputstreamwriter.write) ..................الحل بسيط نسبيا. ما عليك سوى جعل طريقة التشغيل الكاملة () متزامنة وأضف الكلمة الرئيسية المتزامنة ؛ ومع ذلك ، لم يحل المؤلف الموقف الذي قد يضيع فيه السجل إذا أراد حقًا كتابته وكانت سرعة الكتابة سريعة بما فيه الكفاية ؛
يعد استخدام المؤقت للتنفيذ أكثر بساطة ، ولكن إذا تم تنفيذ المهام في المؤقت لفترة طويلة جدًا ، فسيشغلها بشكل حصري كائن الموقت ، مما يجعل المهام اللاحقة غير قادرة على تنفيذها في أي وقت. الحل هو أيضا أبسط. يتم استخدام فئة Thread Pool Timer Class SchedeDexecutorService لتنفيذ ما يلي:
/ ** * */ package net.csdn.blog ؛ استيراد java.io.file ؛ استيراد java.io.ioException ؛ استيراد java.text.simpledateformat ؛ استيراد java.util.date ؛ استيراد java.util.concurrent.executors ؛ استيراد java.util.concurrent.timeunit ؛ استيراد org.apache.log4j.fileappender ؛ استيراد org.apache.log4j.layout ؛ استيراد org.apache.log4j.helpers.loglog ؛ /** * Author coder_xia * <p> * استخدم ScripedExecutorService لتنفيذ سجلات طباعة التكوين المحددة * <p> * / /الفئة العامة ScheduledExecutorserviceAppender يمتد FileAppender { /** * نمط التاريخ. بشكل افتراضي ، يتم تعيين النمط على ". 'yyyyy-mm-dd" * يعني التمديد اليومي. */ private Static Final String DatePattern = "'.'yyyy-mm-dd-hh-mm'.log'" ؛ / *** وقت الفاصل ، الوحدة: دقائق*/ private int intertimtime = 10 ؛ SimplEdateFormat SDF = جديد spileDateFormat (DatePattern) ؛ /*** لا يفعل المنشئ الافتراضي شيئًا. */Public SchedudExecutorServiceAppender () {}/** * Instantiate A <code> ScripedExecutorServiceAppender </code> وافتح الملف * المصمم بواسطة <code> filename </code>. سيصبح اسم الملف المفتوح * وجهة OUPUT لهذا الجيد. */ Public ScheduleDexecutorServiceAppender (تخطيط تخطيط ، اسم ملف السلسلة) يلقي ioException {super (تخطيط ، اسم ملف ، صحيح) ؛ ActivateOptions () ؛ } / ** * regurn the intertimtime * / public int getInterValTime () {return vertaltime ؛ } / ** * param intertimtime * فترة زمنية لتعيين * / public void setInterValtime (int intertaltime) {this.Intervaltime = intertaltime ؛ } Override public void ActivateOptions () {super.activaloptions () ؛ Executors.NewSingLethReadScheduleDexecutor (). ScheduleAtfixedRate (New LogTimerTask () ، 1 ، الفاصل الزمني * 60000 ، timeUnit.milliseconds) ؛ } class logtimertask تنفذ Runnable {Override public void run () {String datedFilename = filename + sdf.format (new date ()) ؛ CloseFile () ؛ ملف ملف = ملف جديد (مستجمعات) ؛ if (target.exists ()) target.delete () ؛ ملف الملف = ملف جديد (اسم الملف) ؛ نتيجة منطقية = file.renameto (الهدف) ؛ if (النتيجة) loglog.debug (اسم الملف + " ->" + مؤرخة Filename) ؛ else loglog.error ("فشل في إعادة تسمية [" + fileName + "] إلى [" + datedfilename + "].") ؛ حاول {setFile (اسم الملف ، صحيح ، bufferedio ، bufferSize) ؛ } catch (ioException e) {errorHandler.error ("setFile (" + filename + "، true) فشل الاتصال.") ؛ }}}}}فيما يتعلق بتنفيذ التوقيت ، هذه هي النهاية تقريبًا. الافتراضي هو إنشاء ملف سجل جديد في 10 دقائق. يمكنك تعيينه بنفسك عند تكوينه. ومع ذلك ، هناك خطر خفي. إذا كان الشخص التكوين لا يعلم أن الفاصل الزمني هو دقائق ، إذا كنت تعتقد أنها ثواني ، فستكون لديك 600 ثم افتح تصحيحًا لإنشاء ملف سجل مع G ، فسيكون بالتأكيد كارثة. يتمثل التحول التالي في الجمع بين الحد الأقصى لحجم RollingFileAppender والحد الأقصى لعدد ملفات النسخ الاحتياطي للمطابقة ، ثم تحسينه مرة أخرى. في المرة القادمة ، سنستمر في وصف عملية التحول.
إضافة تكوين اسم الوحدة النمطية
لقد ذكرت تنفيذ الفئة المخصصة للطباعة Timed Log4J ، لذلك لن أتحدث عن الحجم المحدد وعدد ملفات النسخ الاحتياطي. يمكنني إضافته من رمز نسخ فئة RollingFileAppender إلى الفئة المخصصة السابقة. الشيء الوحيد الذي يجب حله هو مشكلة التزامن ، أي عند إغلاق ملف إعادة تسمية ، سيتم الإبلاغ عن خطأ في إخراج دفق الإخراج عند حدوث حدث سجل.
يوجد الآن سيناريو التطبيق ، وغالبًا ما يكون هناك:
1. يحتوي المشروع على مشاريع مختلفة متعددة ؛
2. نفس المشروع يحتوي على وحدات مختلفة.
للحالة الأولى ، يمكنك تكوين log4j <catogery = "test"> ثم استخدام الطريقة التالية عند إنشاء مسجل:
logger logger = logger.getLogger ("test") ؛في الحالة الثانية ، نأمل أن نطبع وحدات مختلفة في نفس ملف السجل ، لكننا نأمل أن نطبع اسم الوحدة النمطية في السجل لتحديد المشكلة عندما تكون هناك مشكلة. لذلك ، تحتاج هذه المقالة إلى إضافة Modulename التكوين إلى فئة الجسد. لنبدأ التحول أدناه. على عكس الطباعة الموقوتة ، نستخدم فئة RollingFileAppender كطبقة أساسية للتحول.
أولاً ، أضف Modulename عنصر التكوين وأضف أساليب Get and Set ؛
نظرًا لأنها ورثتها من RollingFilePender ، فأنت بحاجة فقط إلى تنسيق البيانات في تسجيل الدخول في subappend () ، وإضافة طريقة formatinfo لتنسيق البيانات ، ويتم حذف الكود ؛
فئة المنتج النهائية هي كما يلي:
حزمة net.csdn.blog ؛ استيراد org.apache.log4j.category ؛ استيراد org.apache.log4j.rollingfileappender ؛ استيراد org.apache.log4j.spi.loggingevent ؛ / ** * Author CORER_XIA * */ MODULEAPPENDER للطبقة العامة يمتد RollingFiLeAppender {private String Modulename ؛ / ** * return the Modulename */ public string getModulename () {return Modulename ؛ } / ** * param modulename * modulename لتعيين * / public void setModulename (سلسلة modulename) {this.modulename = modulename ؛ } / ** * تنسيق محتوى طباعة * * param الحدث * الحدث * return msg * / private string formatinfo (حدث loggingevent) {StringBuilder sb = new StringBuilder () ؛ if (modulename! = null) {sb.append (modulename) .Append ("|") ؛ sb.append (event.getMessage ()) ؛ } return sb.toString () ؛ } Override public void subappend (حدث loggingevent) {String msg = formatinfo (event) ؛ super.subappend (New LoggingEvent (category.class.getName () ، event .getLogger () ، event.getlevel () ، msg ، null)) ؛ }}