Repository: open-telemetry/opentelemetry-java
File: sdk/logs/src/main/java/io/opentelemetry/sdk/logs/LogRecordProcessor.java (Java)

Code before:
import java.util.concurrent.TimeUnit;
import javax.annotation.concurrent.ThreadSafe;

/**
 * {@link LogRecordProcessor} is the interface to allow synchronous hooks for log records emitted by
 * {@link Logger}s.
 *
 * @since 1.27.0
 */
@ThreadSafe
public interface LogRecordProcessor extends Closeable {

  /**
   * Returns a {@link LogRecordProcessor} which simply delegates to all processing to the {@code
   * processors} in order.
   */
  static LogRecordProcessor composite(LogRecordProcessor... processors) {
    return composite(Arrays.asList(processors));
  }

  /**
   * Returns a {@link LogRecordProcessor} which simply delegates to all processing to the {@code
   * processors} in order.
   *
   * <p>Note that if there are {@link BatchLogRecordProcessor} including, they are enforced to end
   * of the processor pipeline.
   */
  static LogRecordProcessor composite(Iterable<LogRecordProcessor> processors) {
    List<LogRecordProcessor> processorList = new ArrayList<>();
    List<BatchLogRecordProcessor> batchProcessors = new ArrayList<>();
    for (LogRecordProcessor processor : processors) {
      if (!BatchLogRecordProcessor.class.equals(processor.getClass())) {
        processorList.add(processor);
      } else {
        batchProcessors.add((BatchLogRecordProcessor) processor);
      }
    }
    processorList.addAll(batchProcessors);

    if (processorList.isEmpty()) {
      return NoopLogRecordProcessor.getInstance();
    }

    if (processorList.size() == 1) {
      return processorList.get(0);
    }

    return MultiLogRecordProcessor.create(processorList);
  }

  /**

Review comment (performance):
This seems too opinionated about order and doesn't work in all cases. For example, its possible to wrap a batch processor with a custom processor implementation which performs filtering before passing to the delegate.

Curious what you think about my [comment here](https://github.com/open-telemetry/opentelemetry-java/issues/6599#issuecomment-2246030350) to suggesting adding `SdkLoggerProviderBuilder#pushLogRecordProcessor` (or equivalent naming) to allow adding processors to the front of the queue. 

Then, when you're customizing `SdkLoggerProviderBuilder`, if you have a processor which mutates you push to to the front, and if you have a processor which batches / exports data, you add it to the end.

There's still the chance of error of course, but we avoid being overly opinionated and potentially accidentally blocking a situation we haven't yet seen.



Code after:
import javax.annotation.concurrent.ThreadSafe;

/**
 * {@link LogRecordProcessor} is the interface to allow synchronous hooks for log records emitted by
 * {@link Logger}s.
 *
 * @since 1.27.0
 */
@ThreadSafe
public interface LogRecordProcessor extends Closeable {

  /**
   * Returns a {@link LogRecordProcessor} which simply delegates to all processing to the {@code
   * processors} in order.
   */
  static LogRecordProcessor composite(LogRecordProcessor... processors) {
    return composite(Arrays.asList(processors));
  }

  /**
   * Returns a {@link LogRecordProcessor} which simply delegates to all processing to the {@code
   * processors} in order.
   */
  static LogRecordProcessor composite(Iterable<LogRecordProcessor> processors) {
    List<LogRecordProcessor> processorList = new ArrayList<>();
    for (LogRecordProcessor processor : processors) {
      processorList.add(processor);
    }
    if (processorList.isEmpty()) {
      return NoopLogRecordProcessor.getInstance();
    }
    if (processorList.size() == 1) {
      return processorList.get(0);
    }
    return MultiLogRecordProcessor.create(processorList);
  }

  /**
   * Called when a {@link Logger} {@link LogRecordBuilder#emit()}s a log record.
   *
   * @param context the context set via {@link LogRecordBuilder#setContext(Context)}, or {@link
   *     Context#current()} if not explicitly set
   * @param logRecord the log record
   */
  void onEmit(Context context, ReadWriteLogRecord logRecord);

  /**
   * Shutdown the log processor.
   *
   * @return result
   */