content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package org.apache.cassandra.db.virtual;
  
  import java.util.Date;
  import java.util.Iterator;
  import java.util.List;
  
  import com.google.common.annotations.VisibleForTesting;
  
  import ch.qos.logback.classic.spi.LoggingEvent;
  import org.apache.cassandra.config.CassandraRelevantProperties;
  import org.apache.cassandra.db.DecoratedKey;
  import org.apache.cassandra.db.marshal.Int32Type;
  import org.apache.cassandra.db.marshal.TimestampType;
  import org.apache.cassandra.db.marshal.UTF8Type;
  import org.apache.cassandra.dht.LocalPartitioner;
  import org.apache.cassandra.schema.TableMetadata;
  
  /**
   * Virtual table for holding Cassandra logs. Entries to this table are added via log appender.
   * <p>
   * The virtual table is bounded in its size. If a new log message is appended to virtual table,
   * the oldest one is removed.
   * <p>
   * This virtual table can be truncated.
   * <p>
   * This table does not enable {@code ALLOW FILTERING} implicitly.
   *
   * @see <a href="https://issues.apache.org/jira/browse/CASSANDRA-18238">CASSANDRA-18238</a>
   * @see org.apache.cassandra.utils.logging.VirtualTableAppender
   */
  public final class LogMessagesTable extends AbstractLoggerVirtualTable<LoggingEvent>
  {
      public static final int LOGS_VIRTUAL_TABLE_DEFAULT_ROWS = 50_000;
      public static final int LOGS_VIRTUAL_TABLE_MAX_ROWS = 100_000;
  
      public static final String TABLE_NAME = "system_logs";
      private static final String TABLE_COMMENT = "Cassandra logs";
  
      public static final String TIMESTAMP_COLUMN_NAME = "timestamp";
      public static final String LOGGER_COLUMN_NAME = "logger";
      public static final String ORDER_IN_MILLISECOND_COLUMN_NAME = "order_in_millisecond";
      public static final String LEVEL_COLUMN_NAME = "level";
      public static final String MESSAGE_COLUMN_NAME = "message";
  
      LogMessagesTable(String keyspace)
      {
          this(keyspace, resolveBufferSize(CassandraRelevantProperties.LOGS_VIRTUAL_TABLE_MAX_ROWS.getInt(),
                                           LOGS_VIRTUAL_TABLE_MAX_ROWS,
                                           LOGS_VIRTUAL_TABLE_DEFAULT_ROWS));
      }
  
      @VisibleForTesting
      LogMessagesTable(String keyspace, int size)
      {
          super(TableMetadata.builder(keyspace, TABLE_NAME)
                             .comment(TABLE_COMMENT)
                             .kind(TableMetadata.Kind.VIRTUAL)
                             .partitioner(new LocalPartitioner(TimestampType.instance))
                             .addPartitionKeyColumn(TIMESTAMP_COLUMN_NAME, TimestampType.instance)
                             .addClusteringColumn(ORDER_IN_MILLISECOND_COLUMN_NAME, Int32Type.instance)
                             .addRegularColumn(LOGGER_COLUMN_NAME, UTF8Type.instance)
                             .addRegularColumn(LEVEL_COLUMN_NAME, UTF8Type.instance)
                             .addRegularColumn(MESSAGE_COLUMN_NAME, UTF8Type.instance).build(),
                size);
      }
  
      @Override
      public List<LoggingEvent> getMessages(LoggingEvent event)
      {
          return List.of(event);
      }
  
      @Override
      public DataSet data()
      {
          SimpleDataSet result = new SimpleDataSet(metadata(), DecoratedKey.comparator.reversed());
  
          synchronized (buffer)
          {
              long milliSecondsOfPreviousLog = 0;
              long milliSecondsOfCurrentLog;
  
              int index = 0;
  
              Iterator<LoggingEvent> iterator = buffer.listIterator();
              while (iterator.hasNext())
              {
                  LoggingEvent log = iterator.next();
  
                  milliSecondsOfCurrentLog = log.getTimeStamp();
                  if (milliSecondsOfPreviousLog == milliSecondsOfCurrentLog)
                      ++index;
                  else
                      index = 0;
  
                  milliSecondsOfPreviousLog = milliSecondsOfCurrentLog;
  
                  result.row(new Date(milliSecondsOfCurrentLog), index)
                        .column(LOGGER_COLUMN_NAME, log.getLoggerName())
                        .column(LEVEL_COLUMN_NAME, log.getLevel().toString())
                        .column(MESSAGE_COLUMN_NAME, log.getFormattedMessage());
              }
          }
  
          return result;
      }
  }
  