001    /*
002     * CDDL HEADER START
003     *
004     * The contents of this file are subject to the terms of the
005     * Common Development and Distribution License, Version 1.0 only
006     * (the "License").  You may not use this file except in compliance
007     * with the License.
008     *
009     * You can obtain a copy of the license at
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.loggers.debug;
028    import org.opends.messages.Message;
029    
030    import org.opends.server.api.*;
031    import org.opends.server.loggers.*;
032    import org.opends.server.types.*;
033    import org.opends.server.util.ServerConstants;
034    import org.opends.server.util.StaticUtils;
035    import org.opends.server.util.TimeThread;
036    import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
037    import static org.opends.server.util.StaticUtils.getFileForPath;
038    import static org.opends.server.util.ServerConstants.PROPERTY_DEBUG_TARGET;
039    import org.opends.server.admin.std.server.DebugTargetCfg;
040    import org.opends.server.admin.std.server.FileBasedDebugLogPublisherCfg;
041    import org.opends.server.admin.std.server.DebugLogPublisherCfg;
042    import org.opends.server.admin.std.meta.DebugLogPublisherCfgDefn;
043    import org.opends.server.admin.server.ConfigurationChangeListener;
044    import org.opends.server.admin.server.ConfigurationDeleteListener;
045    import org.opends.server.admin.server.ConfigurationAddListener;
046    import org.opends.server.config.ConfigException;
047    import org.opends.server.core.DirectoryServer;
048    import static org.opends.messages.ConfigMessages.
049        ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER;
050    import static org.opends.messages.ConfigMessages.
051       ERR_CONFIG_LOGGING_CANNOT_OPEN_FILE;
052    import static org.opends.messages.ConfigMessages.
053        ERR_CONFIG_LOGGING_INSANE_MODE;
054    import static org.opends.messages.ConfigMessages.
055        ERR_CONFIG_LOGGING_MODE_INVALID;
056    
057    
058    import java.util.*;
059    import java.io.File;
060    import java.io.IOException;
061    
062    import com.sleepycat.je.*;
063    
064    /**
065     * The debug log publisher implementation that writes debug messages to files
066     * on disk. It also maintains the rotation and retention polices of the log
067     * files.
068     */
069    public class TextDebugLogPublisher
070        extends DebugLogPublisher<FileBasedDebugLogPublisherCfg>
071        implements ConfigurationChangeListener<FileBasedDebugLogPublisherCfg>,
072                   ConfigurationAddListener<DebugTargetCfg>,
073                   ConfigurationDeleteListener<DebugTargetCfg>
074    {
075      private static long globalSequenceNumber;
076    
077      private TextWriter writer;
078    
079      private FileBasedDebugLogPublisherCfg currentConfig;
080    
081      /**
082       * Returns an instance of the text debug log publisher that will print
083       * all messages to the provided writer. This is used to print the messages
084       * to the console when the server starts up. By default, only error level
085       * messages are printed. Special debug targets are also parsed from
086       * system properties if any are specified.
087       *
088       * @param writer The text writer where the message will be written to.
089       * @return The instance of the text error log publisher that will print
090       * all messages to standard out.
091       */
092      public static TextDebugLogPublisher
093          getStartupTextDebugPublisher(TextWriter writer)
094      {
095        TextDebugLogPublisher startupPublisher = new TextDebugLogPublisher();
096        startupPublisher.writer = writer;
097    
098        Set<Map.Entry<Object, Object>> propertyEntries =
099            System.getProperties().entrySet();
100        for(Map.Entry<Object, Object> entry : propertyEntries)
101        {
102          if(((String)entry.getKey()).startsWith(PROPERTY_DEBUG_TARGET))
103          {
104            String value = (String)entry.getValue();
105            int settingsStart= value.indexOf(":");
106    
107            //See if the scope and settings exists
108            if(settingsStart > 0)
109            {
110              String scope = value.substring(0, settingsStart);
111              TraceSettings settings =
112                  TraceSettings.parseTraceSettings(
113                      value.substring(settingsStart+1));
114              if(settings != null)
115              {
116                startupPublisher.addTraceSettings(scope, settings);
117              }
118            }
119          }
120        }
121    
122        return startupPublisher;
123      }
124    
125      /**
126       * {@inheritDoc}
127       */
128      public boolean isConfigurationAcceptable(DebugLogPublisherCfg configuration,
129                                               List<Message> unacceptableReasons)
130      {
131        FileBasedDebugLogPublisherCfg config =
132            (FileBasedDebugLogPublisherCfg) configuration;
133        return isConfigurationChangeAcceptable(config, unacceptableReasons);
134      }
135    
136      /**
137       * {@inheritDoc}
138       */
139      public void initializeDebugLogPublisher(FileBasedDebugLogPublisherCfg config)
140          throws ConfigException, InitializationException
141      {
142        File logFile = getFileForPath(config.getLogFile());
143        FileNamingPolicy fnPolicy = new TimeStampNaming(logFile);
144    
145        try
146        {
147          FilePermission perm =
148              FilePermission.decodeUNIXMode(config.getLogFilePermissions());
149    
150          LogPublisherErrorHandler errorHandler =
151              new LogPublisherErrorHandler(config.dn());
152    
153          boolean writerAutoFlush =
154              config.isAutoFlush() && !config.isAsynchronous();
155    
156          MultifileTextWriter writer =
157              new MultifileTextWriter("Multifile Text Writer for " +
158                  config.dn().toNormalizedString(),
159                                      config.getTimeInterval(),
160                                      fnPolicy,
161                                      perm,
162                                      errorHandler,
163                                      "UTF-8",
164                                      writerAutoFlush,
165                                      config.isAppend(),
166                                      (int)config.getBufferSize());
167    
168          // Validate retention and rotation policies.
169          for(DN dn : config.getRotationPolicyDNs())
170          {
171            writer.addRotationPolicy(DirectoryServer.getRotationPolicy(dn));
172          }
173    
174          for(DN dn: config.getRetentionPolicyDNs())
175          {
176            writer.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn));
177          }
178    
179          if(config.isAsynchronous())
180          {
181            this.writer = new AsyncronousTextWriter("Asyncronous Text Writer for " +
182                config.dn().toNormalizedString(), config.getQueueSize(),
183                                                  config.isAutoFlush(), writer);
184          }
185          else
186          {
187            this.writer = writer;
188          }
189        }
190        catch(DirectoryException e)
191        {
192          Message message = ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get(
193              config.dn().toString(), String.valueOf(e));
194          throw new InitializationException(message, e);
195    
196        }
197        catch(IOException e)
198        {
199          Message message = ERR_CONFIG_LOGGING_CANNOT_OPEN_FILE.get(
200              logFile.toString(), config.dn().toString(), String.valueOf(e));
201          throw new InitializationException(message, e);
202    
203        }
204    
205    
206        config.addDebugTargetAddListener(this);
207        config.addDebugTargetDeleteListener(this);
208    
209        //Get the default/global settings
210        LogLevel logLevel =
211            DebugLogLevel.parse(config.getDefaultDebugLevel().toString());
212        Set<LogCategory> logCategories = null;
213        if(!config.getDefaultDebugCategory().isEmpty())
214        {
215          logCategories =
216              new HashSet<LogCategory>(config.getDefaultDebugCategory().size());
217          for(DebugLogPublisherCfgDefn.DefaultDebugCategory category :
218              config.getDefaultDebugCategory())
219          {
220            logCategories.add(DebugLogCategory.parse(category.toString()));
221          }
222        }
223    
224        TraceSettings defaultSettings =
225            new TraceSettings(logLevel, logCategories,
226                              config.isDefaultOmitMethodEntryArguments(),
227                              config.isDefaultOmitMethodReturnValue(),
228                              config.getDefaultThrowableStackFrames(),
229                              config.isDefaultIncludeThrowableCause());
230    
231        addTraceSettings(null, defaultSettings);
232    
233        for(String name : config.listDebugTargets())
234        {
235          DebugTargetCfg targetCfg = config.getDebugTarget(name);
236    
237          addTraceSettings(targetCfg.getDebugScope(), new TraceSettings(targetCfg));
238        }
239    
240        currentConfig = config;
241    
242        config.addFileBasedDebugChangeListener(this);
243      }
244    
245    
246    
247      /**
248       * {@inheritDoc}
249       */
250      public boolean isConfigurationChangeAcceptable(
251          FileBasedDebugLogPublisherCfg config, List<Message> unacceptableReasons)
252      {
253        // Make sure the permission is valid.
254        try
255        {
256          FilePermission filePerm =
257              FilePermission.decodeUNIXMode(config.getLogFilePermissions());
258          if(!filePerm.isOwnerWritable())
259          {
260            Message message = ERR_CONFIG_LOGGING_INSANE_MODE.get(
261                config.getLogFilePermissions());
262            unacceptableReasons.add(message);
263            return false;
264          }
265        }
266        catch(DirectoryException e)
267        {
268          Message message = ERR_CONFIG_LOGGING_MODE_INVALID.get(
269              config.getLogFilePermissions(), String.valueOf(e));
270          unacceptableReasons.add(message);
271          return false;
272        }
273    
274        return true;
275      }
276    
277      /**
278       * {@inheritDoc}
279       */
280      public ConfigChangeResult applyConfigurationChange(
281          FileBasedDebugLogPublisherCfg config)
282      {
283        // Default result code.
284        ResultCode resultCode = ResultCode.SUCCESS;
285        boolean adminActionRequired = false;
286        ArrayList<Message> messages = new ArrayList<Message>();
287    
288        //Get the default/global settings
289        LogLevel logLevel =
290            DebugLogLevel.parse(config.getDefaultDebugLevel().toString());
291        Set<LogCategory> logCategories = null;
292        if(!config.getDefaultDebugCategory().isEmpty())
293        {
294          logCategories =
295              new HashSet<LogCategory>(config.getDefaultDebugCategory().size());
296          for(DebugLogPublisherCfgDefn.DefaultDebugCategory category :
297              config.getDefaultDebugCategory())
298          {
299            logCategories.add(DebugLogCategory.parse(category.toString()));
300          }
301        }
302    
303        TraceSettings defaultSettings =
304            new TraceSettings(logLevel, logCategories,
305                              config.isDefaultOmitMethodEntryArguments(),
306                              config.isDefaultOmitMethodReturnValue(),
307                              config.getDefaultThrowableStackFrames(),
308                              config.isDefaultIncludeThrowableCause());
309    
310        addTraceSettings(null, defaultSettings);
311    
312        DebugLogger.updateTracerSettings();
313    
314        File logFile = getFileForPath(config.getLogFile());
315        FileNamingPolicy fnPolicy = new TimeStampNaming(logFile);
316    
317        try
318        {
319          FilePermission perm =
320              FilePermission.decodeUNIXMode(config.getLogFilePermissions());
321    
322          boolean writerAutoFlush =
323              config.isAutoFlush() && !config.isAsynchronous();
324    
325          TextWriter currentWriter;
326          // Determine the writer we are using. If we were writing asyncronously,
327          // we need to modify the underlaying writer.
328          if(writer instanceof AsyncronousTextWriter)
329          {
330            currentWriter = ((AsyncronousTextWriter)writer).getWrappedWriter();
331          }
332          else
333          {
334            currentWriter = writer;
335          }
336    
337          if(currentWriter instanceof MultifileTextWriter)
338          {
339            MultifileTextWriter mfWriter = (MultifileTextWriter)writer;
340    
341            mfWriter.setNamingPolicy(fnPolicy);
342            mfWriter.setFilePermissions(perm);
343            mfWriter.setAppend(config.isAppend());
344            mfWriter.setAutoFlush(writerAutoFlush);
345            mfWriter.setBufferSize((int)config.getBufferSize());
346            mfWriter.setInterval(config.getTimeInterval());
347    
348            mfWriter.removeAllRetentionPolicies();
349            mfWriter.removeAllRotationPolicies();
350    
351            for(DN dn : config.getRotationPolicyDNs())
352            {
353              mfWriter.addRotationPolicy(DirectoryServer.getRotationPolicy(dn));
354            }
355    
356            for(DN dn: config.getRetentionPolicyDNs())
357            {
358              mfWriter.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn));
359            }
360    
361            if(writer instanceof AsyncronousTextWriter && !config.isAsynchronous())
362            {
363              // The asynronous setting is being turned off.
364              AsyncronousTextWriter asyncWriter = ((AsyncronousTextWriter)writer);
365              writer = mfWriter;
366              asyncWriter.shutdown(false);
367            }
368    
369            if(!(writer instanceof AsyncronousTextWriter) &&
370                config.isAsynchronous())
371            {
372              // The asynronous setting is being turned on.
373              AsyncronousTextWriter asyncWriter =
374                  new AsyncronousTextWriter("Asyncronous Text Writer for " +
375                      config.dn().toNormalizedString(), config.getQueueSize(),
376                                                        config.isAutoFlush(),
377                                                        mfWriter);
378              writer = asyncWriter;
379            }
380    
381            if((currentConfig.isAsynchronous() && config.isAsynchronous()) &&
382                (currentConfig.getQueueSize() != config.getQueueSize()))
383            {
384              adminActionRequired = true;
385            }
386    
387            currentConfig = config;
388          }
389        }
390        catch(Exception e)
391        {
392          Message message = ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get(
393                  config.dn().toString(),
394                  stackTraceToSingleLineString(e));
395          resultCode = DirectoryServer.getServerErrorResultCode();
396          messages.add(message);
397    
398        }
399    
400        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
401      }
402    
403      /**
404       * {@inheritDoc}
405       */
406      public boolean isConfigurationAddAcceptable(DebugTargetCfg config,
407                                                  List<Message> unacceptableReasons)
408      {
409        return getTraceSettings(config.getDebugScope()) == null;
410      }
411    
412      /**
413       * {@inheritDoc}
414       */
415      public boolean isConfigurationDeleteAcceptable(DebugTargetCfg config,
416                                                  List<Message> unacceptableReasons)
417      {
418        // A delete should always be acceptable.
419        return true;
420      }
421    
422      /**
423       * {@inheritDoc}
424       */
425      public ConfigChangeResult applyConfigurationAdd(DebugTargetCfg config)
426      {
427        // Default result code.
428        ResultCode resultCode = ResultCode.SUCCESS;
429        boolean adminActionRequired = false;
430        ArrayList<Message> messages = new ArrayList<Message>();
431    
432        addTraceSettings(config.getDebugScope(), new TraceSettings(config));
433    
434        DebugLogger.updateTracerSettings();
435    
436        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
437      }
438    
439      /**
440       * {@inheritDoc}
441       */
442      public ConfigChangeResult applyConfigurationDelete(DebugTargetCfg config)
443      {
444        // Default result code.
445        ResultCode resultCode = ResultCode.SUCCESS;
446        boolean adminActionRequired = false;
447        ArrayList<Message> messages = new ArrayList<Message>();
448    
449        removeTraceSettings(config.getDebugScope());
450    
451        DebugLogger.updateTracerSettings();
452    
453        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
454      }
455    
456      /**
457       * {@inheritDoc}
458       */
459      public void traceConstructor(LogLevel level,
460                                   TraceSettings settings,
461                                   String signature,
462                                   String sourceLocation,
463                                   Object[] args,
464                                   StackTraceElement[] stackTrace)
465      {
466        LogCategory category = DebugLogCategory.CONSTRUCTOR;
467    
468        String msg = "";
469        if(args != null)
470        {
471          msg = buildDefaultEntryMessage(args);
472        }
473    
474        String stack = null;
475        if(stackTrace != null)
476        {
477          stack = DebugStackTraceFormatter.formatStackTrace(stackTrace,
478                                                            settings.stackDepth);
479        }
480        publish(category, level, signature, sourceLocation, msg, stack);
481      }
482    
483      /**
484       * {@inheritDoc}
485       */
486      public void traceMethodEntry(LogLevel level,
487                                   TraceSettings settings,
488                                   String signature,
489                                   String sourceLocation,
490                                   Object obj,
491                                   Object[] args,
492                                   StackTraceElement[] stackTrace)
493      {
494        LogCategory category = DebugLogCategory.ENTER;
495        String msg = "";
496        if(args != null)
497        {
498          msg = buildDefaultEntryMessage(args);
499        }
500    
501        String stack = null;
502        if(stackTrace != null)
503        {
504          stack = DebugStackTraceFormatter.formatStackTrace(stackTrace,
505                                                            settings.stackDepth);
506        }
507        publish(category, level, signature, sourceLocation, msg, stack);
508      }
509    
510      /**
511       * {@inheritDoc}
512       */
513      public void traceStaticMethodEntry(LogLevel level,
514                                         TraceSettings settings,
515                                         String signature,
516                                         String sourceLocation,
517                                         Object[] args,
518                                         StackTraceElement[] stackTrace)
519      {
520        LogCategory category = DebugLogCategory.ENTER;
521        String msg = "";
522        if(args != null)
523        {
524          msg = buildDefaultEntryMessage(args);
525        }
526    
527        String stack = null;
528        if(stackTrace != null)
529        {
530          stack = DebugStackTraceFormatter.formatStackTrace(stackTrace,
531                                                            settings.stackDepth);
532        }
533        publish(category, level, signature, sourceLocation, msg, stack);
534      }
535    
536      /**
537       * {@inheritDoc}
538       */
539      public void traceReturn(LogLevel level,
540                              TraceSettings settings,
541                              String signature,
542                              String sourceLocation,
543                              Object ret,
544                              StackTraceElement[] stackTrace)
545      {
546        LogCategory category = DebugLogCategory.EXIT;
547        String msg = "";
548        if(ret != null)
549        {
550          msg = DebugMessageFormatter.format("returned={%s}",
551                                             new Object[] {ret});
552        }
553    
554        String stack = null;
555        if(stackTrace != null)
556        {
557          stack = DebugStackTraceFormatter.formatStackTrace(stackTrace,
558                                                            settings.stackDepth);
559        }
560        publish(category, level, signature, sourceLocation, msg, stack);
561      }
562    
563      /**
564       * {@inheritDoc}
565       */
566      public void traceThrown(LogLevel level,
567                              TraceSettings settings,
568                              String signature,
569                              String sourceLocation,
570                              Throwable ex,
571                              StackTraceElement[] stackTrace)
572      {
573        LogCategory category = DebugLogCategory.THROWN;
574    
575        String msg = DebugMessageFormatter.format("thrown={%s}",
576                                                  new Object[] {ex});
577    
578        String stack = null;
579        if(stackTrace != null)
580        {
581          stack = DebugStackTraceFormatter.formatStackTrace(ex,
582                                                            settings.stackDepth,
583                                                            settings.includeCause);
584        }
585        publish(category, level, signature, sourceLocation, msg, stack);
586      }
587    
588      /**
589       * {@inheritDoc}
590       */
591      public void traceMessage(LogLevel level,
592                               TraceSettings settings,
593                               String signature,
594                               String sourceLocation,
595                               String msg,
596                               StackTraceElement[] stackTrace)
597      {
598        LogCategory category = DebugLogCategory.MESSAGE;
599    
600        String stack = null;
601        if(stackTrace != null)
602        {
603          stack = DebugStackTraceFormatter.formatStackTrace(stackTrace,
604                                                            settings.stackDepth);
605        }
606        publish(category, level, signature, sourceLocation, msg, stack);
607      }
608    
609      /**
610       * {@inheritDoc}
611       */
612      public void traceCaught(LogLevel level,
613                              TraceSettings settings,
614                              String signature,
615                              String sourceLocation,
616                              Throwable ex,
617                              StackTraceElement[] stackTrace)
618      {
619        LogCategory category = DebugLogCategory.CAUGHT;
620        String msg = DebugMessageFormatter.format("caught={%s}",
621                                                  new Object[] {ex});
622    
623        String stack = null;
624        if(stackTrace != null)
625        {
626          stack = DebugStackTraceFormatter.formatStackTrace(ex,
627                                                            settings.stackDepth,
628                                                            settings.includeCause);
629        }
630        publish(category, level, signature, sourceLocation, msg, stack);
631      }
632    
633      /**
634       * {@inheritDoc}
635       */
636      public void traceJEAccess(LogLevel level,
637                                TraceSettings settings,
638                                String signature,
639                                String sourceLocation,
640                                OperationStatus status,
641                                Database database, Transaction txn,
642                                DatabaseEntry key, DatabaseEntry data,
643                                StackTraceElement[] stackTrace)
644      {
645        LogCategory category = DebugLogCategory.DATABASE_ACCESS;
646    
647        // Build the string that is common to category DATABASE_ACCESS.
648        StringBuilder builder = new StringBuilder();
649        builder.append(" (");
650        builder.append(status.toString());
651        builder.append(")");
652        builder.append(" db=");
653        try
654        {
655          builder.append(database.getDatabaseName());
656        }
657        catch(DatabaseException de)
658        {
659          builder.append(de.toString());
660        }
661        if (txn != null)
662        {
663          builder.append(" txnid=");
664          try
665          {
666            builder.append(txn.getId());
667          }
668          catch(DatabaseException de)
669          {
670            builder.append(de.toString());
671          }
672        }
673        else
674        {
675          builder.append(" txnid=none");
676        }
677    
678        builder.append(ServerConstants.EOL);
679        if(key != null)
680        {
681          builder.append("key:");
682          builder.append(ServerConstants.EOL);
683          StaticUtils.byteArrayToHexPlusAscii(builder, key.getData(), 4);
684        }
685    
686        // If the operation was successful we log the same common information
687        // plus the data
688        if (status == OperationStatus.SUCCESS && data != null)
689        {
690    
691          builder.append("data(len=");
692          builder.append(data.getSize());
693          builder.append("):");
694          builder.append(ServerConstants.EOL);
695          StaticUtils.byteArrayToHexPlusAscii(builder, data.getData(), 4);
696    
697        }
698    
699        String stack = null;
700        if(stackTrace != null)
701        {
702          stack = DebugStackTraceFormatter.formatStackTrace(stackTrace,
703                                                            settings.stackDepth);
704        }
705        publish(category, level, signature, sourceLocation,
706                builder.toString(), stack);
707      }
708    
709      /**
710       * {@inheritDoc}
711       */
712      public void traceData(LogLevel level,
713                            TraceSettings settings,
714                            String signature,
715                            String sourceLocation,
716                            byte[] data,
717                            StackTraceElement[] stackTrace)
718      {
719        LogCategory category = DebugLogCategory.DATA;
720        if(data != null)
721        {
722          StringBuilder builder = new StringBuilder();
723          builder.append(ServerConstants.EOL);
724          builder.append("data(len=");
725          builder.append(data.length);
726          builder.append("):");
727          builder.append(ServerConstants.EOL);
728          StaticUtils.byteArrayToHexPlusAscii(builder, data, 4);
729    
730        String stack = null;
731        if(stackTrace != null)
732        {
733          stack = DebugStackTraceFormatter.formatStackTrace(stackTrace,
734                                                            settings.stackDepth);
735        }
736        publish(category, level, signature, sourceLocation,
737                builder.toString(), stack);
738        }
739      }
740    
741      /**
742       * {@inheritDoc}
743       */
744      public void traceProtocolElement(LogLevel level,
745                                       TraceSettings settings,
746                                       String signature,
747                                       String sourceLocation,
748                                       ProtocolElement element,
749                                       StackTraceElement[] stackTrace)
750      {
751        LogCategory category = DebugLogCategory.PROTOCOL;
752    
753        StringBuilder builder = new StringBuilder();
754        builder.append(ServerConstants.EOL);
755        element.toString(builder, 4);
756    
757        String stack = null;
758        if(stackTrace != null)
759        {
760          stack = DebugStackTraceFormatter.formatStackTrace(stackTrace,
761                                                            settings.stackDepth);
762        }
763        publish(category, level, signature, sourceLocation,
764                builder.toString(), stack);
765      }
766    
767      /**
768       * {@inheritDoc}
769       */
770      public void close()
771      {
772        writer.shutdown();
773    
774        if(currentConfig != null)
775        {
776          currentConfig.removeFileBasedDebugChangeListener(this);
777        }
778      }
779    
780    
781      // Publishes a record, optionally performing some "special" work:
782      // - injecting a stack trace into the message
783      // - format the message with argument values
784      private void publish(LogCategory category, LogLevel level, String signature,
785                           String sourceLocation, String msg, String stack)
786      {
787        Thread thread = Thread.currentThread();
788    
789        StringBuilder buf = new StringBuilder();
790        // Emit the timestamp.
791        buf.append("[");
792        buf.append(TimeThread.getLocalTime());
793        buf.append("] ");
794    
795        // Emit the seq num
796        buf.append(globalSequenceNumber++);
797        buf.append(" ");
798    
799        // Emit debug category.
800        buf.append(category);
801        buf.append(" ");
802    
803        // Emit the debug level.
804        buf.append(level);
805        buf.append(" ");
806    
807        // Emit thread info.
808        buf.append("thread={");
809        buf.append(thread.getName());
810        buf.append("(");
811        buf.append(thread.getId());
812        buf.append(")} ");
813    
814        if(thread instanceof DirectoryThread)
815        {
816          buf.append("threadDetail={");
817          for (Map.Entry<String, String> entry :
818            ((DirectoryThread) thread).getDebugProperties().entrySet())
819          {
820            buf.append(entry.getKey());
821            buf.append("=");
822            buf.append(entry.getValue());
823            buf.append(" ");
824          }
825          buf.append("} ");
826        }
827    
828        // Emit method info.
829        buf.append("method={");
830        buf.append(signature);
831        buf.append("(");
832        buf.append(sourceLocation);
833        buf.append(")} ");
834    
835        // Emit message.
836        buf.append(msg);
837    
838        // Emit Stack Trace.
839        if(stack != null)
840        {
841          buf.append("\nStack Trace:\n");
842          buf.append(stack);
843        }
844    
845        writer.writeRecord(buf.toString());
846      }
847    
848      private String buildDefaultEntryMessage(Object[] args)
849      {
850        StringBuilder format = new StringBuilder();
851        for (int i = 0; i < args.length; i++)
852        {
853          if (i != 0) format.append(", ");
854          format.append("arg");
855          format.append(i + 1);
856          format.append("={%s}");
857        }
858    
859        return DebugMessageFormatter.format(format.toString(), args);
860      }
861    
862      /**
863       * {@inheritDoc}
864       */
865      public DN getDN()
866      {
867        if(currentConfig != null)
868        {
869          return currentConfig.dn();
870        }
871        else
872        {
873          return null;
874        }
875      }
876    }