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 legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2009 Sun Microsystems, Inc.
025 *      Portions Copyright 2012-2015 ForgeRock AS.
026 */
027package org.opends.server.loggers;
028
029import static org.opends.messages.ConfigMessages.*;
030import static org.opends.messages.LoggerMessages.*;
031import static org.opends.server.util.ServerConstants.*;
032import static org.opends.server.util.StaticUtils.*;
033
034import java.io.File;
035import java.io.IOException;
036import java.util.Arrays;
037import java.util.HashSet;
038import java.util.List;
039import java.util.Set;
040import java.util.StringTokenizer;
041
042import org.forgerock.i18n.LocalizableMessage;
043import org.forgerock.opendj.config.server.ConfigException;
044import org.opends.messages.Severity;
045import org.opends.server.admin.server.ConfigurationChangeListener;
046import org.opends.server.admin.std.meta.ErrorLogPublisherCfgDefn;
047import org.opends.server.admin.std.server.FileBasedErrorLogPublisherCfg;
048import org.opends.server.core.DirectoryServer;
049import org.opends.server.core.ServerContext;
050import org.forgerock.opendj.config.server.ConfigChangeResult;
051import org.opends.server.types.DN;
052import org.opends.server.types.DirectoryException;
053import org.opends.server.types.FilePermission;
054import org.opends.server.types.InitializationException;
055import org.opends.server.util.StaticUtils;
056import org.opends.server.util.TimeThread;
057
058/**
059 * This class provides an implementation of an error log publisher.
060 */
061public class TextErrorLogPublisher
062    extends ErrorLogPublisher<FileBasedErrorLogPublisherCfg>
063    implements ConfigurationChangeListener<FileBasedErrorLogPublisherCfg>
064{
065  private TextWriter writer;
066  private FileBasedErrorLogPublisherCfg currentConfig;
067
068  /**
069   * Returns a new text error log publisher which will print all messages to the
070   * provided writer. This publisher should be used by tools.
071   *
072   * @param writer
073   *          The text writer where the message will be written to.
074   * @return A new text error log publisher which will print all messages to the
075   *         provided writer.
076   */
077  public static TextErrorLogPublisher getToolStartupTextErrorPublisher(TextWriter writer)
078  {
079    TextErrorLogPublisher startupPublisher = new TextErrorLogPublisher();
080    startupPublisher.writer = writer;
081    startupPublisher.defaultSeverities.addAll(Arrays.asList(Severity.values()));
082    return startupPublisher;
083  }
084
085
086
087  /**
088   * Returns a new text error log publisher which will print only notices,
089   * severe warnings and errors, and fatal errors messages to the provided
090   * writer. This less verbose publisher should be used by the directory server
091   * during startup.
092   *
093   * @param writer
094   *          The text writer where the message will be written to.
095   * @return A new text error log publisher which will print only notices,
096   *         severe warnings and errors, and fatal errors messages to the
097   *         provided writer.
098   */
099  public static TextErrorLogPublisher getServerStartupTextErrorPublisher(
100      TextWriter writer)
101  {
102    TextErrorLogPublisher startupPublisher = new TextErrorLogPublisher();
103    startupPublisher.writer = writer;
104    startupPublisher.defaultSeverities.addAll(Arrays.asList(
105        Severity.ERROR, Severity.WARNING, Severity.NOTICE));
106    return startupPublisher;
107  }
108
109
110
111  /** {@inheritDoc} */
112  @Override
113  public void initializeLogPublisher(FileBasedErrorLogPublisherCfg config, ServerContext serverContext)
114      throws ConfigException, InitializationException
115  {
116    File logFile = getFileForPath(config.getLogFile());
117    FileNamingPolicy fnPolicy = new TimeStampNaming(logFile);
118
119    try
120    {
121      FilePermission perm =
122          FilePermission.decodeUNIXMode(config.getLogFilePermissions());
123
124      LogPublisherErrorHandler errorHandler =
125          new LogPublisherErrorHandler(config.dn());
126
127      boolean writerAutoFlush =
128          config.isAutoFlush() && !config.isAsynchronous();
129
130      MultifileTextWriter writer = new MultifileTextWriter("Multifile Text Writer for " + config.dn(),
131                                  config.getTimeInterval(),
132                                  fnPolicy,
133                                  perm,
134                                  errorHandler,
135                                  "UTF-8",
136                                  writerAutoFlush,
137                                  config.isAppend(),
138                                  (int)config.getBufferSize());
139
140      // Validate retention and rotation policies.
141      for(DN dn : config.getRotationPolicyDNs())
142      {
143        writer.addRotationPolicy(DirectoryServer.getRotationPolicy(dn));
144      }
145
146      for(DN dn: config.getRetentionPolicyDNs())
147      {
148        writer.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn));
149      }
150
151      if(config.isAsynchronous())
152      {
153        this.writer = new AsynchronousTextWriter("Asynchronous Text Writer for " + config.dn(),
154            config.getQueueSize(), config.isAutoFlush(), writer);
155      }
156      else
157      {
158        this.writer = writer;
159      }
160    }
161    catch(DirectoryException e)
162    {
163      throw new InitializationException(
164          ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get(config.dn(), e), e);
165    }
166    catch(IOException e)
167    {
168      throw new InitializationException(
169          ERR_CONFIG_LOGGING_CANNOT_OPEN_FILE.get(logFile, config.dn(), e), e);
170    }
171
172    setDefaultSeverities(config.getDefaultSeverity());
173
174    for(String overrideSeverity : config.getOverrideSeverity())
175    {
176      if(overrideSeverity != null)
177      {
178        int equalPos = overrideSeverity.indexOf('=');
179        if (equalPos < 0)
180        {
181          throw new ConfigException(WARN_ERROR_LOGGER_INVALID_OVERRIDE_SEVERITY.get(overrideSeverity));
182        }
183
184        String category = overrideSeverity.substring(0, equalPos);
185        category = category.replace("-", "_").toUpperCase();
186        try
187        {
188          Set<Severity> severities = new HashSet<>();
189          StringTokenizer sevTokenizer = new StringTokenizer(overrideSeverity.substring(equalPos + 1), ",");
190          while (sevTokenizer.hasMoreElements())
191          {
192            String severityName = sevTokenizer.nextToken();
193            severityName = severityName.replace("-", "_").toUpperCase();
194            if (LOG_SEVERITY_ALL.equalsIgnoreCase(severityName))
195            {
196              severities.add(Severity.ERROR);
197              severities.add(Severity.WARNING);
198              severities.add(Severity.NOTICE);
199              severities.add(Severity.INFORMATION);
200            }
201            else
202            {
203              try
204              {
205                severities.add(Severity.parseString(severityName));
206              }
207              catch (Exception e)
208              {
209                throw new ConfigException(WARN_ERROR_LOGGER_INVALID_SEVERITY.get(severityName));
210              }
211            }
212          }
213          definedSeverities.put(category, severities);
214        }
215        catch (Exception e)
216        {
217          throw new ConfigException(WARN_ERROR_LOGGER_INVALID_CATEGORY.get(category));
218        }
219      }
220    }
221
222    currentConfig = config;
223
224    config.addFileBasedErrorChangeListener(this);
225  }
226
227
228
229  /** {@inheritDoc} */
230  @Override
231  public boolean isConfigurationAcceptable(
232      FileBasedErrorLogPublisherCfg config, List<LocalizableMessage> unacceptableReasons)
233  {
234    return isConfigurationChangeAcceptable(config, unacceptableReasons);
235  }
236
237  /** {@inheritDoc} */
238  @Override
239  public boolean isConfigurationChangeAcceptable(
240      FileBasedErrorLogPublisherCfg config, List<LocalizableMessage> unacceptableReasons)
241  {
242    // Make sure the permission is valid.
243    try
244    {
245      FilePermission filePerm = FilePermission.decodeUNIXMode(config.getLogFilePermissions());
246      if(!filePerm.isOwnerWritable())
247      {
248        unacceptableReasons.add(ERR_CONFIG_LOGGING_INSANE_MODE.get(config.getLogFilePermissions()));
249        return false;
250      }
251    }
252    catch(DirectoryException e)
253    {
254      unacceptableReasons.add(ERR_CONFIG_LOGGING_MODE_INVALID.get(config.getLogFilePermissions(), e));
255      return false;
256    }
257
258    for(String overrideSeverity : config.getOverrideSeverity())
259    {
260      if(overrideSeverity != null)
261      {
262        int equalPos = overrideSeverity.indexOf('=');
263        if (equalPos < 0)
264        {
265          unacceptableReasons.add(WARN_ERROR_LOGGER_INVALID_OVERRIDE_SEVERITY.get(overrideSeverity));
266          return false;
267        }
268
269        // No check on category because it can be any value
270        StringTokenizer sevTokenizer = new StringTokenizer(overrideSeverity.substring(equalPos + 1), ",");
271        while (sevTokenizer.hasMoreElements())
272        {
273          String severityName = sevTokenizer.nextToken();
274          severityName = severityName.replace("-", "_").toUpperCase();
275          if (!LOG_SEVERITY_ALL.equalsIgnoreCase(severityName))
276          {
277            try
278            {
279              Severity.parseString(severityName);
280            }
281            catch (Exception e)
282            {
283              unacceptableReasons.add(WARN_ERROR_LOGGER_INVALID_SEVERITY.get(severityName));
284              return false;
285            }
286          }
287        }
288      }
289    }
290    return true;
291  }
292
293  /** {@inheritDoc} */
294  @Override
295  public ConfigChangeResult applyConfigurationChange(FileBasedErrorLogPublisherCfg config)
296  {
297    final ConfigChangeResult ccr = new ConfigChangeResult();
298
299    setDefaultSeverities(config.getDefaultSeverity());
300
301    definedSeverities.clear();
302    for(String overrideSeverity : config.getOverrideSeverity())
303    {
304      if(overrideSeverity != null)
305      {
306        int equalPos = overrideSeverity.indexOf('=');
307        if (equalPos < 0)
308        {
309          ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
310          ccr.addMessage(WARN_ERROR_LOGGER_INVALID_OVERRIDE_SEVERITY.get(overrideSeverity));
311        } else
312        {
313          String category = overrideSeverity.substring(0, equalPos);
314          category = category.replace("-", "_").toUpperCase();
315          try
316          {
317            Set<Severity> severities = new HashSet<>();
318            StringTokenizer sevTokenizer =
319              new StringTokenizer(overrideSeverity.substring(equalPos+1), ",");
320            while (sevTokenizer.hasMoreElements())
321            {
322              String severityName = sevTokenizer.nextToken();
323              severityName = severityName.replace("-", "_").toUpperCase();
324              if(LOG_SEVERITY_ALL.equalsIgnoreCase(severityName))
325              {
326                severities.add(Severity.ERROR);
327                severities.add(Severity.INFORMATION);
328                severities.add(Severity.WARNING);
329                severities.add(Severity.NOTICE);
330              }
331              else
332              {
333                try
334                {
335                  severities.add(Severity.parseString(severityName));
336                }
337                catch(Exception e)
338                {
339                  throw new ConfigException(WARN_ERROR_LOGGER_INVALID_SEVERITY.get(severityName));
340                }
341              }
342            }
343            definedSeverities.put(category, severities);
344          }
345          catch(Exception e)
346          {
347            ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
348            ccr.addMessage(WARN_ERROR_LOGGER_INVALID_CATEGORY.get(category));
349          }
350        }
351      }
352    }
353
354    File logFile = getFileForPath(config.getLogFile());
355    FileNamingPolicy fnPolicy = new TimeStampNaming(logFile);
356    try
357    {
358      FilePermission perm = FilePermission.decodeUNIXMode(config.getLogFilePermissions());
359
360      boolean writerAutoFlush =
361          config.isAutoFlush() && !config.isAsynchronous();
362
363      TextWriter currentWriter;
364      // Determine the writer we are using. If we were writing asynchronously,
365      // we need to modify the underlying writer.
366      if(writer instanceof AsynchronousTextWriter)
367      {
368        currentWriter = ((AsynchronousTextWriter)writer).getWrappedWriter();
369      }
370      else
371      {
372        currentWriter = writer;
373      }
374
375      if(currentWriter instanceof MultifileTextWriter)
376      {
377        MultifileTextWriter mfWriter = (MultifileTextWriter)currentWriter;
378
379        mfWriter.setNamingPolicy(fnPolicy);
380        mfWriter.setFilePermissions(perm);
381        mfWriter.setAppend(config.isAppend());
382        mfWriter.setAutoFlush(writerAutoFlush);
383        mfWriter.setBufferSize((int)config.getBufferSize());
384        mfWriter.setInterval(config.getTimeInterval());
385
386        mfWriter.removeAllRetentionPolicies();
387        mfWriter.removeAllRotationPolicies();
388
389        for(DN dn : config.getRotationPolicyDNs())
390        {
391          mfWriter.addRotationPolicy(DirectoryServer.getRotationPolicy(dn));
392        }
393
394        for(DN dn: config.getRetentionPolicyDNs())
395        {
396          mfWriter.addRetentionPolicy(DirectoryServer.getRetentionPolicy(dn));
397        }
398
399        if(writer instanceof AsynchronousTextWriter && !config.isAsynchronous())
400        {
401          // The asynchronous setting is being turned off.
402          AsynchronousTextWriter asyncWriter = (AsynchronousTextWriter)writer;
403          writer = mfWriter;
404          asyncWriter.shutdown(false);
405        }
406
407        if (!(writer instanceof AsynchronousTextWriter) && config.isAsynchronous())
408        {
409          // The asynchronous setting is being turned on.
410          writer = new AsynchronousTextWriter("Asynchronous Text Writer for " + config.dn(),
411              config.getQueueSize(), config.isAutoFlush(), mfWriter);
412        }
413
414        if (currentConfig.isAsynchronous()
415            && config.isAsynchronous()
416            && currentConfig.getQueueSize() != config.getQueueSize())
417        {
418          ccr.setAdminActionRequired(true);
419        }
420
421        currentConfig = config;
422      }
423    }
424    catch(Exception e)
425    {
426      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
427      ccr.addMessage(ERR_CONFIG_LOGGING_CANNOT_CREATE_WRITER.get(
428          config.dn(), stackTraceToSingleLineString(e)));
429    }
430
431    return ccr;
432  }
433
434  private void setDefaultSeverities(Set<ErrorLogPublisherCfgDefn.DefaultSeverity> defSevs)
435  {
436    defaultSeverities.clear();
437    if (defSevs.isEmpty())
438    {
439      defaultSeverities.add(Severity.ERROR);
440      defaultSeverities.add(Severity.WARNING);
441    }
442    else
443    {
444      for (ErrorLogPublisherCfgDefn.DefaultSeverity defSev : defSevs)
445      {
446        String defaultSeverity = defSev.toString();
447        if (LOG_SEVERITY_ALL.equalsIgnoreCase(defaultSeverity))
448        {
449          defaultSeverities.add(Severity.ERROR);
450          defaultSeverities.add(Severity.WARNING);
451          defaultSeverities.add(Severity.INFORMATION);
452          defaultSeverities.add(Severity.NOTICE);
453        }
454        else if (LOG_SEVERITY_NONE.equalsIgnoreCase(defaultSeverity))
455        {
456          // don't add any severity
457        }
458        else
459        {
460          Severity errorSeverity = Severity.parseString(defSev.name());
461          if (errorSeverity != null)
462          {
463            defaultSeverities.add(errorSeverity);
464          }
465        }
466      }
467    }
468  }
469
470  /** {@inheritDoc} */
471  @Override
472  public void close()
473  {
474    writer.shutdown();
475
476    if(currentConfig != null)
477    {
478      currentConfig.removeFileBasedErrorChangeListener(this);
479    }
480  }
481
482  /** {@inheritDoc} */
483  @Override
484  public void log(String category, Severity severity, LocalizableMessage message, Throwable exception)
485  {
486    if (isEnabledFor(category, severity))
487    {
488      StringBuilder sb = new StringBuilder();
489      sb.append("[");
490      sb.append(TimeThread.getLocalTime());
491      sb.append("] category=").append(category).
492      append(" severity=").append(severity).
493      append(" msgID=").append(message.resourceName())
494                       .append('.')
495                       .append(message.ordinal()).
496      append(" msg=").append(message);
497      if (exception != null)
498      {
499        sb.append(" exception=").append(
500            StaticUtils.stackTraceToSingleLineString(exception));
501      }
502
503      writer.writeRecord(sb.toString());
504    }
505  }
506
507  /** {@inheritDoc} */
508  @Override
509  public boolean isEnabledFor(String category, Severity severity)
510  {
511    Set<Severity> severities = definedSeverities.get(category);
512    if (severities == null)
513    {
514      severities = defaultSeverities;
515    }
516    return severities.contains(severity);
517  }
518
519  /** {@inheritDoc} */
520  @Override
521  public DN getDN()
522  {
523    if(currentConfig != null)
524    {
525      return currentConfig.dn();
526    }
527    return null;
528  }
529}
530