Пример #1
0
 /**
  * @param event
  * @param depth
  * @param lfc
  * @param ndcStr
  */
 private void start(LoggingEvent event, int depth, TimelineLifecycle lfc, String ndcStr) {
   NdcContext ndcCtxt = getNdcContext(lfc);
   if (ndcCtxt.getNdcObj() != null)
     throw new IllegalStateException(
         "ZMonitor Log4j stack Operation Logic Error or forget to cleanup the state.");
   ndcCtxt.doStart(createName(event, null), event.getRenderedMessage(), ndcStr, depth);
 }
Пример #2
0
  @Override
  protected void append(LoggingEvent logEvent) {
    final String message = logEvent.getRenderedMessage();
    final Level level = logEvent.getLevel();
    final String streamId = getStreamId(level);

    final Display display = Display.getDefault();
    final Thread thread = display.getThread();
    Thread _currentThread = Thread.currentThread();
    final boolean uiThread = Objects.equal(thread, _currentThread);
    final Runnable _function =
        new Runnable() {
          @Override
          public void run() {
            try {
              final IOConsole console = AntlrConsoleFactory.getConsole();
              final IOConsoleOutputStream stream = console.newOutputStream();
              final ConsoleColorProvider colorProvider = new ConsoleColorProvider();
              final Color color = colorProvider.getColor(streamId);
              stream.setColor(color);
              stream.setActivateOnWrite(true);
              stream.write((message + "\n"));
              stream.close();
            } catch (Throwable ex) {
              ex.printStackTrace();
            }
          }
        };
    final Runnable printTask = _function;
    if (uiThread) {
      printTask.run();
    } else {
      display.syncExec(printTask);
    }
  }
Пример #3
0
 private LogMessageEntity convertToLogMessageEntity(LoggingEvent loggingEvent)
     throws UnknownHostException {
   return new LogMessageEntity(
       loggingEvent.getTimeStamp(),
       loggingEvent.getRenderedMessage(),
       new PatternLayout("%C{1}").format(loggingEvent),
       (String) loggingEvent.getMDC(MDCHelper.MDC_TRANSACTION_ID),
       String.format("%s / %s", nodeId, InetAddress.getLocalHost().getHostName()));
 }
Пример #4
0
  /**
   * Encodes a LoggingEvent into a HashMap using the logstash JSON format.
   *
   * @param loggingEvent The LoggingEvent to encode.
   * @param includeLocationInfo Whether to include LocationInfo in the map, or not.
   * @return A Map representing the LoggingEvent, which is suitable to be serialized by a JSON
   *     encoder such as Jackson.
   */
  @SuppressWarnings("rawtypes")
  public static Map<String, Object> encodeToMap(
      LoggingEvent loggingEvent, boolean includeLocationInfo) {
    Map<String, Object> logstashEvent = new LoggingEventMap();
    String threadName = loggingEvent.getThreadName();
    long timestamp = loggingEvent.getTimeStamp();
    HashMap<String, Object> exceptionInformation = new HashMap<String, Object>();
    Map mdc = loggingEvent.getProperties();
    String ndc = loggingEvent.getNDC();

    logstashEvent.put("@version", VERSION);
    logstashEvent.put("@timestamp", dateFormat(timestamp));
    logstashEvent.put("source_host", getHostname());
    logstashEvent.put("message", loggingEvent.getRenderedMessage());

    if (loggingEvent.getThrowableInformation() != null) {
      final ThrowableInformation throwableInformation = loggingEvent.getThrowableInformation();
      if (throwableInformation.getThrowable().getClass().getCanonicalName() != null) {
        exceptionInformation.put(
            "exception_class", throwableInformation.getThrowable().getClass().getCanonicalName());
      }
      if (throwableInformation.getThrowable().getMessage() != null) {
        exceptionInformation.put(
            "exception_message", throwableInformation.getThrowable().getMessage());
      }
      if (throwableInformation.getThrowableStrRep() != null) {
        StringBuilder stackTrace = new StringBuilder();
        for (String line : throwableInformation.getThrowableStrRep()) {
          stackTrace.append(line);
          stackTrace.append("\n");
        }
        exceptionInformation.put("stacktrace", stackTrace);
      }
      logstashEvent.put("exception", exceptionInformation);
    }

    if (includeLocationInfo) {
      LocationInfo info = loggingEvent.getLocationInformation();
      logstashEvent.put("file", info.getFileName());
      logstashEvent.put("line_number", info.getLineNumber());
      logstashEvent.put("class", info.getClassName());
      logstashEvent.put("method", info.getMethodName());
    }

    logstashEvent.put("logger_name", loggingEvent.getLoggerName());
    logstashEvent.put("mdc", mdc);
    logstashEvent.put("ndc", ndc);
    logstashEvent.put("level", loggingEvent.getLevel().toString());
    logstashEvent.put("thread_name", threadName);

    return logstashEvent;
  }
Пример #5
0
  /**
   * Creates a new <code>EventDetails</code> instance.
   *
   * @param aEvent a <code>LoggingEvent</code> value
   */
  EventDetails(LoggingEvent aEvent) {

    this(
        aEvent.timeStamp,
        aEvent.getLevel(),
        aEvent.getLoggerName(),
        aEvent.getNDC(),
        aEvent.getThreadName(),
        aEvent.getRenderedMessage(),
        aEvent.getThrowableStrRep(),
        (aEvent.getLocationInformation() == null)
            ? null
            : aEvent.getLocationInformation().fullInfo);
  }
 /*     */ public String convert(LoggingEvent event) /*     */ {
   /* 393 */ switch (this.type) {
       /*     */ case 2000:
       /* 395 */ return Long.toString(event.timeStamp - LoggingEvent.getStartTime());
       /*     */ case 2001:
       /* 397 */ return event.getThreadName();
       /*     */ case 2002:
       /* 399 */ return event.getLevel().toString();
       /*     */ case 2003:
       /* 401 */ return event.getNDC();
       /*     */ case 2004:
       /* 403 */ return event.getRenderedMessage();
       /*     */ }
   /* 405 */ return null;
   /*     */ }
Пример #7
0
    public void doAppend(LoggingEvent arg0) {
      String logger = arg0.getLoggerName();
      String message = arg0.getRenderedMessage();
      Level level = arg0.getLevel();

      Set toIgnore = (Set) m_ignore.get(logger);
      if (toIgnore != null) {
        // if any of the strings in the set start our message, skip it
        for (Iterator i = toIgnore.iterator(); i.hasNext(); ) {
          String start = (String) i.next();
          if (message.startsWith(start)) return;
        }
      }

      m_other.doAppend(arg0);
    }
Пример #8
0
  @Override
  public String format(LoggingEvent event) {
    if (sbuf.capacity() > MAX_CAPACITY) {
      sbuf = new StringBuffer(BUF_SIZE);
    } else {
      sbuf.setLength(0);
    }

    sbuf.append(Layout.LINE_SEP + "<tr>" + Layout.LINE_SEP);

    sbuf.append("<td title=\"Level\">");
    if (event.getLevel().equals(Level.DEBUG)) {
      sbuf.append("<font color=\"#339933\">");
      sbuf.append(Transform.escapeTags(String.valueOf(event.getLevel())));
      sbuf.append("</font>");
    } else if (event.getLevel().isGreaterOrEqual(Level.WARN)) {
      sbuf.append("<font color=\"#993300\"><strong>");
      sbuf.append(Transform.escapeTags(String.valueOf(event.getLevel())));
      sbuf.append("</strong></font>");
    } else {
      sbuf.append(Transform.escapeTags(String.valueOf(event.getLevel())));
    }
    sbuf.append("</td>" + Layout.LINE_SEP);

    sbuf.append("<td title=\"Message\">");
    sbuf.append(Transform.escapeTags(event.getRenderedMessage()));
    sbuf.append("</td>" + Layout.LINE_SEP);
    sbuf.append("</tr>" + Layout.LINE_SEP);

    if (event.getNDC() != null) {
      sbuf.append(
          "<tr><td bgcolor=\"#EEEEEE\" style=\"font-size : xx-small;\" colspan=\"6\" title=\"Nested Diagnostic Context\">");
      sbuf.append("NDC: " + Transform.escapeTags(event.getNDC()));
      sbuf.append("</td></tr>" + Layout.LINE_SEP);
    }

    String[] s = event.getThrowableStrRep();
    if (s != null) {
      sbuf.append(
          "<tr><td bgcolor=\"#993300\" style=\"color:White; font-size : xx-small;\" colspan=\"6\">");
      appendThrowableAsHTML(s, sbuf);
      sbuf.append("</td></tr>" + Layout.LINE_SEP);
    }

    return sbuf.toString();
  }
Пример #9
0
 public String convert(LoggingEvent event) {
   switch (type) {
     case RELATIVE_TIME_CONVERTER:
       return (String.valueOf(event.timeStamp - LoggingEvent.getStartTime()));
     case THREAD_CONVERTER:
       return event.getThreadName();
     case LEVEL_CONVERTER:
       return event.getLevel().toString();
     case NDC_CONVERTER:
       return event.getNDC();
     case MESSAGE_CONVERTER:
       {
         return event.getRenderedMessage();
       }
     default:
       return null;
   }
 }
Пример #10
0
 @Override
 protected void append(LoggingEvent event) {
   try {
     buf.reset();
     writeHeader(event);
     writeSP();
     writeStructuredData();
     writeSP();
     if (prefixMessageWithBOM) writeBOM();
     writeString(event.getRenderedMessage());
     protocol.send(this);
   } catch (IOException e) {
     errorHandler.error(
         "Failed to emit message by " + protocol + " connection to " + host + ":" + port,
         e,
         ErrorCode.WRITE_FAILURE,
         event);
   }
 }
Пример #11
0
  /**
   * Developer will use log4j's: NDC.push() & NDC.pop() to command ZMonitor to do: tl.start() &
   * tl.end()
   *
   * <p>ASSUMPTION: user wont use ZMonitor directly while using log4j NDC, the current tl.depth will
   * always match to the last NDC state we may consider the direct operation of timeline in the
   * future.
   *
   * <p>Base on this concept:
   *
   * <p>If: current NDC Depth > last NDC Depth( or there's no last NDC Depth) we need to do
   * tl.start(), and push current NDC Depth. (check : if currentTlDepth != lastTlDepth do failover.)
   *
   * <p>If: current NDC Depth = last NDC Depth we simply do tl.record().
   *
   * <p>If: current NDC Depth < last NDC Depth if: current NDC Depth = last.last NDC Depth We call
   * tl.end() and pop NDC stack, because user is telling us he want to end the current stack. (check
   * : if currentTlDepth != lastTlDepth do failover.)
   *
   * <p>if: current NDC Depth > last.last NDC Depth We simply do tl.record() because it's not reach
   * the end yet. (check : if currentTlDepth != lastTlDepth do failover.)
   *
   * <p>if: current NDC Depth < last.last NDC Depth We recursively do tl.end with Pop NDCStack and
   * renew the last NDC depth till this condition is not satisfied.
   *
   * @param event
   * @param message
   * @param ndcDepth
   * @param ndcStr
   */
  private void record(LoggingEvent event, int ndcDepth, TimelineLifecycle lfc, String ndcStr) {

    // TODO: how to figure out this part?
    // condition 1. Timeline already started.

    NdcContext ndcCtxt = getNdcContext(lfc);
    NdcObj last = ndcCtxt.getNdcObj();

    //		if(last.tlDepth != getCurrentTlDepth(lfc)){
    //			//TODO: Timeline is operated between two LOG4J log commands, the stack information might be
    // f****d up!
    //			/*
    //			 * Should I allow user to mix log4j with native ZMonitor API?
    //			 */
    //		}

    String mesg = event.getRenderedMessage();
    if (last == null) {
      ndcCtxt.doRecord(createName(event, null), mesg, ndcDepth);
      return;
    }

    if (ndcDepth > last.depth) {
      ndcCtxt.doStart(createName(event, null), mesg, ndcStr, ndcDepth);

    } else if (ndcDepth == last.depth) {
      ndcCtxt.doRecord(createName(event, null), mesg, ndcDepth);

    } else { // if( ndcDepth < last.depth )
      if (ndcDepth == last.previous.depth) {
        ndcCtxt.doEnd(createName(event, "L4J_END"), mesg);

      } else if (ndcDepth > last.previous.depth) {
        ndcCtxt.doRecord(createName(event, null), mesg, ndcDepth);

      } else { // if(ndcDepth < last.previous.depth)
        autoEnd(ndcCtxt);
        record(event, ndcDepth, lfc, ndcStr); // recursive call...
        return;
      }
    }
  }
Пример #12
0
  private ErrorEventStat convert(LoggingEvent loggingEvent) {
    ErrorEventStat errorEventStat = new ErrorEventStat();
    LocationInfo locationInformation = loggingEvent.getLocationInformation();

    errorEventStat.setEmail(email);
    errorEventStat.setClientVersion(getArtifactId());
    errorEventStat.setOs(System.getProperty(SystemConstants.OS_NAME_PROP_KEY));
    errorEventStat.setOsVersion(System.getProperty(SystemConstants.OS_VERSION_PROP_KEY));
    errorEventStat.setJvm(System.getProperty(SystemConstants.JAVA_VERSION_PROP_KEY));
    errorEventStat.setThreadName(loggingEvent.getThreadName());
    errorEventStat.setMessage(loggingEvent.getRenderedMessage());
    errorEventStat.setFileName(locationInformation.getFileName());
    errorEventStat.setLineNumber(locationInformation.getLineNumber());
    errorEventStat.setMethodName(locationInformation.getMethodName());
    errorEventStat.setClassName(locationInformation.getClassName());

    ThrowableInformation throwableInformation = loggingEvent.getThrowableInformation();
    if (throwableInformation != null) {
      errorEventStat.setStackTrace(throwableInformation.getThrowable().getMessage());
    }

    return errorEventStat;
  }
Пример #13
0
  protected void append(LoggingEvent event) {
    Connection connection = null;
    try {
      connection = connectionSource.getConnection();
      connection.setAutoCommit(false);

      PreparedStatement insertStatement;
      if (cnxSupportsGetGeneratedKeys) {
        insertStatement = connection.prepareStatement(insertSQL, Statement.RETURN_GENERATED_KEYS);
      } else {
        insertStatement = connection.prepareStatement(insertSQL);
      }

      /*          insertStatement.setLong(1, event.getSequenceNumber());*/
      insertStatement.setLong(1, 0);

      insertStatement.setLong(2, event.getTimeStamp());
      insertStatement.setString(3, event.getRenderedMessage());
      insertStatement.setString(4, event.getLoggerName());
      insertStatement.setString(5, event.getLevel().toString());
      insertStatement.setString(6, event.getNDC());
      insertStatement.setString(7, event.getThreadName());
      insertStatement.setShort(8, DBHelper.computeReferenceMask(event));

      LocationInfo li;

      if (event.locationInformationExists() || locationInfo) {
        li = event.getLocationInformation();
      } else {
        li = LocationInfo.NA_LOCATION_INFO;
      }

      insertStatement.setString(9, li.getFileName());
      insertStatement.setString(10, li.getClassName());
      insertStatement.setString(11, li.getMethodName());
      insertStatement.setString(12, li.getLineNumber());

      int updateCount = insertStatement.executeUpdate();
      if (updateCount != 1) {
        LogLog.warn("Failed to insert loggingEvent");
      }

      ResultSet rs = null;
      Statement idStatement = null;
      boolean gotGeneratedKeys = false;
      if (cnxSupportsGetGeneratedKeys) {
        try {
          rs = (ResultSet) GET_GENERATED_KEYS_METHOD.invoke(insertStatement, null);
          gotGeneratedKeys = true;
        } catch (InvocationTargetException ex) {
          Throwable target = ex.getTargetException();
          if (target instanceof SQLException) {
            throw (SQLException) target;
          }
          throw ex;
        } catch (IllegalAccessException ex) {
          LogLog.warn("IllegalAccessException invoking PreparedStatement.getGeneratedKeys", ex);
        }
      }

      if (!gotGeneratedKeys) {
        insertStatement.close();
        insertStatement = null;

        idStatement = connection.createStatement();
        idStatement.setMaxRows(1);
        rs = idStatement.executeQuery(sqlDialect.getSelectInsertId());
      }

      // A ResultSet cursor is initially positioned before the first row; the
      // first call to the method next makes the first row the current row
      rs.next();
      int eventId = rs.getInt(1);

      rs.close();

      // we no longer need the insertStatement
      if (insertStatement != null) {
        insertStatement.close();
        insertStatement = null;
      }

      if (idStatement != null) {
        idStatement.close();
        idStatement = null;
      }

      Set propertiesKeys = event.getPropertyKeySet();

      if (propertiesKeys.size() > 0) {
        PreparedStatement insertPropertiesStatement =
            connection.prepareStatement(insertPropertiesSQL);

        for (Iterator i = propertiesKeys.iterator(); i.hasNext(); ) {
          String key = (String) i.next();
          String value = event.getProperty(key);

          // LogLog.info("id " + eventId + ", key " + key + ", value " + value);
          insertPropertiesStatement.setInt(1, eventId);
          insertPropertiesStatement.setString(2, key);
          insertPropertiesStatement.setString(3, value);

          if (cnxSupportsBatchUpdates) {
            insertPropertiesStatement.addBatch();
          } else {
            insertPropertiesStatement.execute();
          }
        }

        if (cnxSupportsBatchUpdates) {
          insertPropertiesStatement.executeBatch();
        }

        insertPropertiesStatement.close();
        insertPropertiesStatement = null;
      }

      String[] strRep = event.getThrowableStrRep();

      if (strRep != null) {
        LogLog.debug("Logging an exception");

        PreparedStatement insertExceptionStatement =
            connection.prepareStatement(insertExceptionSQL);

        for (short i = 0; i < strRep.length; i++) {
          insertExceptionStatement.setInt(1, eventId);
          insertExceptionStatement.setShort(2, i);
          insertExceptionStatement.setString(3, strRep[i]);
          if (cnxSupportsBatchUpdates) {
            insertExceptionStatement.addBatch();
          } else {
            insertExceptionStatement.execute();
          }
        }
        if (cnxSupportsBatchUpdates) {
          insertExceptionStatement.executeBatch();
        }
        insertExceptionStatement.close();
        insertExceptionStatement = null;
      }

      connection.commit();
    } catch (Throwable sqle) {
      LogLog.error("problem appending event", sqle);
    } finally {
      DBHelper.closeConnection(connection);
    }
  }
Пример #14
0
    @Override
    public void run() {
      try {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        while (true) {
          final Event event = events.take();
          LoggingEvent logEvent = event.getEvent();

          String name = logEvent.getLogger().getName();
          Level level = logEvent.getLevel();

          MessageProperties amqpProps = new MessageProperties();
          amqpProps.setDeliveryMode(deliveryMode);
          amqpProps.setContentType(contentType);
          if (null != contentEncoding) {
            amqpProps.setContentEncoding(contentEncoding);
          }
          amqpProps.setHeader(CATEGORY_NAME, name);
          amqpProps.setHeader(CATEGORY_LEVEL, level.toString());
          if (generateId) {
            amqpProps.setMessageId(UUID.randomUUID().toString());
          }

          // Set applicationId, if we're using one
          if (null != applicationId) {
            amqpProps.setAppId(applicationId);
          }

          // Set timestamp
          Calendar tstamp = Calendar.getInstance();
          tstamp.setTimeInMillis(logEvent.getTimeStamp());
          amqpProps.setTimestamp(tstamp.getTime());

          // Copy properties in from MDC
          @SuppressWarnings("rawtypes")
          Map props = event.getProperties();
          @SuppressWarnings("unchecked")
          Set<Entry<?, ?>> entrySet = props.entrySet();
          for (Entry<?, ?> entry : entrySet) {
            amqpProps.setHeader(entry.getKey().toString(), entry.getValue());
          }
          LocationInfo locInfo = logEvent.getLocationInformation();
          if (!"?".equals(locInfo.getClassName())) {
            amqpProps.setHeader(
                "location",
                String.format(
                    "%s.%s()[%s]",
                    locInfo.getClassName(), locInfo.getMethodName(), locInfo.getLineNumber()));
          }

          StringBuilder msgBody;
          String routingKey;
          synchronized (layoutMutex) {
            msgBody = new StringBuilder(layout.format(logEvent));
            routingKey = routingKeyLayout.format(logEvent);
          }
          if (layout.ignoresThrowable() && null != logEvent.getThrowableInformation()) {
            ThrowableInformation tinfo = logEvent.getThrowableInformation();
            for (String line : tinfo.getThrowableStrRep()) {
              msgBody.append(String.format("%s%n", line));
            }
          }

          // Send a message
          try {
            Message message = null;
            if (AmqpAppender.this.charset != null) {
              try {
                message =
                    new Message(msgBody.toString().getBytes(AmqpAppender.this.charset), amqpProps);
              } catch (UnsupportedEncodingException e) {
                /* fall back to default */
              }
            }
            if (message == null) {
              message =
                  new Message(
                      msgBody.toString().getBytes(), amqpProps); // NOSONAR (default charset)
            }
            message = postProcessMessageBeforeSend(message, event);
            rabbitTemplate.send(exchangeName, routingKey, message);
          } catch (AmqpException e) {
            int retries = event.incrementRetries();
            if (retries < maxSenderRetries) {
              // Schedule a retry based on the number of times I've tried to re-send this
              retryTimer.schedule(
                  new TimerTask() {
                    @Override
                    public void run() {
                      events.add(event);
                    }
                  },
                  (long) (Math.pow(retries, Math.log(retries)) * 1000));
            } else {
              errorHandler.error(
                  "Could not send log message "
                      + logEvent.getRenderedMessage()
                      + " after "
                      + maxSenderRetries
                      + " retries",
                  e,
                  ErrorCode.WRITE_FAILURE,
                  logEvent);
            }
          }
        }
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
      }
    }
Пример #15
0
  public static final GelfMessage makeMessage(LoggingEvent event, GelfMessageProvider provider) {
    long timeStamp = Log4jVersionChecker.getTimeStamp(event);
    Level level = event.getLevel();

    String file = null;
    String lineNumber = null;
    if (provider.isIncludeLocation()) {
      LocationInfo locationInformation = event.getLocationInformation();
      file = locationInformation.getFileName();
      lineNumber = locationInformation.getLineNumber();
    }

    String renderedMessage = event.getRenderedMessage();
    String shortMessage;

    if (renderedMessage == null) {
      renderedMessage = "";
    }

    if (renderedMessage.length() > MAX_SHORT_MESSAGE_LENGTH) {
      shortMessage = renderedMessage.substring(0, MAX_SHORT_MESSAGE_LENGTH - 1);
    } else {
      shortMessage = renderedMessage;
    }

    if (provider.isExtractStacktrace()) {
      ThrowableInformation throwableInformation = event.getThrowableInformation();
      if (throwableInformation != null) {
        renderedMessage += "\n\r" + extractStacktrace(throwableInformation);
      }
    }

    GelfMessage gelfMessage =
        new GelfMessage(
            shortMessage,
            renderedMessage,
            timeStamp,
            String.valueOf(level.getSyslogEquivalent()),
            lineNumber,
            file);

    if (provider.getOriginHost() != null) {
      gelfMessage.setHost(provider.getOriginHost());
    }

    if (provider.getFacility() != null) {
      gelfMessage.setFacility(provider.getFacility());
    }

    Map<String, String> fields = provider.getFields();
    for (Map.Entry<String, String> entry : fields.entrySet()) {
      if (entry.getKey().equals(ORIGIN_HOST_KEY) && gelfMessage.getHost() == null) {
        gelfMessage.setHost(fields.get(ORIGIN_HOST_KEY));
      } else {
        gelfMessage.addField(entry.getKey(), entry.getValue());
      }
    }

    if (provider.isAddExtendedInformation()) {

      gelfMessage.addField(THREAD_NAME, event.getThreadName());
      gelfMessage.addField(LOGGER_NAME, event.getLoggerName());
      gelfMessage.addField(JAVA_TIMESTAMP, Long.toString(gelfMessage.getJavaTimestamp()));

      // Get MDC and add a GELF field for each key/value pair
      Map<String, Object> mdc = MDC.getContext();

      if (mdc != null) {
        for (Map.Entry<String, Object> entry : mdc.entrySet()) {

          gelfMessage.addField(entry.getKey(), entry.getValue().toString());
        }
      }

      // Get NDC and add a GELF field
      String ndc = event.getNDC();

      if (ndc != null) {

        gelfMessage.addField(LOGGER_NDC, ndc);
      }
    }

    return gelfMessage;
  }
Пример #16
0
 protected void append(LoggingEvent event) {
   messages.add(event.getRenderedMessage());
 }
Пример #17
0
  /*(non-Javadoc)
   * @see org.apache.log4j.AppenderSkeleton#append(org.apache.log4j.spi.LoggingEvent)
   */
  protected void append(LoggingEvent event) {

    String ndcStr = null;
    int depth = -1;
    { // IMPORTANT: this section is something MUST be called!
      ndcStr = event.getNDC();
      depth = NDC.getDepth();
      event.getThreadName();
      // Get a copy of this thread's MDC.
      event.getMDCCopy();
    }

    if (shallRecursionPrevented(this.getClass(), event.getLoggerName())
        || shallRecursionPrevented(ZMLog.class, event.getLoggerName())) return;

    { // IMPORTANT: this section is something MUST be called!
      event.getRenderedMessage();
      event.getThrowableStrRep();

      /*
       * SCENARIO:
       *  while tl!=started, we suppose to start a new tl.
       *  log4j shoulden't terminate other's tl, it can only terminate it's selves.
       *
       *  depth=0, tl==started, controlBySelf		 [complete] this tl is reaching it's end, should be terminated.
       *  depth=0, tl==started, controlByOthers	[recording] this tl is controlled by others, log4j is simply an interceptor, do recording.
       *
       *  depth=0, tl!=started, controlBySelf		[doNothing] because tl can only be started while depth>0.
       *  depth=0, tl!=started, controlByOthers	[doNothing] because tl can only be started while depth>0.
       *
       *  depth>0, tl==started, controlBySelf		[recording] watch out the depth problem.
       *  depth>0, tl==started, controlByOthers	[recording] watch out the depth problem.
       *
       *  depth>0, tl!=started, controlBySelf		 [do Start]
       *  depth>0, tl!=started, controlByOthers	 [do Start]
       *
       */
      TimelineLifecycle lfc = ZMonitorManager.getInstance().getTimelineLifecycle();

      String mesg = event.getRenderedMessage();
      if (depth == 0) {
        if (ZMonitor.isTimelineStarted()) {
          if (isControlledBySelf(lfc)) {
            complete(event, mesg, lfc);
          } else {
            record(event, depth, lfc, ndcStr);
          }
        }
        // do nothing...
        // tl.start must satisfy (depth > 0), otherwise there's no way for appender to know when to
        // complete timeline.
      } else {
        if (ZMonitor.isTimelineStarted()) {
          record(event, depth, lfc, ndcStr);
        } else {
          start(event, depth, lfc, ndcStr);
          setControlledBySelf(lfc);
        }
      }
    }
  }