Write logs with Aliyun Log Java Producer

更新时间:
复制 MD 格式

Aliyun Log Java Producer is a high-performance library for writing logs to SLS from big data engines such as Flink, Spark, and Storm. It supports log compression, batch uploads, and async sending where the standard API or SDK falls short.

Prerequisites

What is Aliyun Log Java Producer

Aliyun Log Java Producer is a high-performance library for Java applications in big data and high-concurrency scenarios. Compared to the standard API or SDK, it provides high performance, separation of compute and I/O logic, and resource management. It leverages the sequential write capability of SLS to ensure ordered log uploads.

For implementation details, see Aliyun LOG Java Producer.

SLS provides sample applications to help you get started quickly: Aliyun Log Producer Sample Application.

Workflow

image

Features

  • Thread safety: All Producer interface methods are thread-safe.

  • Asynchronous sending: The send method returns immediately. The Producer caches and merges data internally, then sends it in batches for higher throughput.

  • Automatic retry: The Producer retries failed sends based on the configured retry count and backoff time.

  • Behavior tracing: Use Callback or Future to check whether data was sent successfully and inspect each send attempt for troubleshooting.

  • Context restoration: Logs from the same Producer instance share a context, so you can view surrounding logs on the server.

  • Graceful shutdown: The close method processes all cached data before exiting, and you receive corresponding notifications.

Benefits

Compared to the standard API or SDK, the Producer provides:

  • High performance

    Writing massive data with limited resources demands complex logic: multi-threading, caching, batch sending, and failure retries. The Producer handles all of this, simplifying development while providing performance advantages.

  • Asynchronous non-blocking

    With sufficient memory, the Producer caches data and returns immediately from the send method, separating compute from I/O. Retrieve the result through the returned Future or the provided Callback.

  • Controllable resources

    Configure the memory limit for cached data and the number of sending threads. This lets you balance resource consumption and write throughput.

  • Simple problem location

    On failure, the Producer returns both a status code and a descriptive error message. For example, "connection timeout" for network issues or "server unresponsive" for unresponsive servers.

Usage notes

  • The aliyun-log-producer calls the PutLogs operation to upload logs. Raw log size limits apply per request. For more information, see Data read and write.

  • SLS resources — projects, Logstores, shards, LogtailConfig, machine groups, LogItem size, LogItem (Key) length, and LogItem (Value) length — are subject to limits. For more information, see Basic resource limits.

  • After the code runs for the first time, enable Logstore indexing in the SLS console and wait one minute before running queries.

  • When querying logs in the console, field values exceeding the maximum length are truncated and excluded from analysis. For more information, see Create indexes.

Billing

SDK costs are the same as console costs. For more information, see Billing overview.

Step 1: Install Aliyun Log Java Producer

To use Aliyun Log Java Producer in a Maven project, add the following dependency to your pom.xml file in <dependencies>:

<dependency>
    <groupId>com.aliyun.openservices</groupId>
    <artifactId>aliyun-log-producer</artifactId>
    <version>0.3.22</version>
</dependency>

If a version conflict occurs, add the following dependency in <dependencies>:

<dependency>
    <groupId>com.aliyun.openservices</groupId>
    <artifactId>aliyun-log</artifactId>
    <version>0.6.114</version>
  <classifier>jar-with-dependencies</classifier>
</dependency>

Step 2: Configure ProducerConfig

ProducerConfig controls the sending policy. Adjust parameters to suit your workload:

Config producerConfig = new ProducerConfig();
producerConfig.setTotalSizeInBytes(104857600);

Parameter

Type

Description

totalSizeInBytes

Integer

The maximum size of logs that can be cached by a producer instance. Default value: 100 MB.

maxBlockMs

Integer

The maximum time (in seconds) the send method blocks when the Producer cache is full. Default: 60 seconds.

If the timeout elapses and cache space remains insufficient, the send method throws TimeoutException.

Set to 0 to throw TimeoutException immediately when cache space is insufficient.

Set to a negative value to block indefinitely until cache space becomes available.

ioThreadCount

Integer

The number of threads for sending tasks. Default: the number of available processors.

batchSizeThresholdInBytes

Integer

The size threshold for sending a batch. Default: 512 KB. Maximum: 5 MB.

batchCountThreshold

Integer

The log count threshold for sending a batch. Default: 4096. Maximum: 40960.

lingerMs

Integer

The linger time before sending a batch. Default: 2 seconds. Minimum: 100 ms.

retries

Integer

The maximum retry count after an initial send failure. Default: 10.

Set to 0 or less to send the batch directly to the failure queue on first failure.

maxReservedAttempts

Integer

Each send attempt generates an attempt record. This parameter controls how many recent attempts are retained. Default: 11.

Higher values provide more detailed tracing at the cost of increased memory usage.

baseRetryBackoffMs

Integer

The initial retry backoff time. Default: 100 milliseconds.

The Producer uses exponential backoff: wait time before the Nth retry = baseRetryBackoffMs × 2^(N-1).

maxRetryBackoffMs

Integer

The maximum retry backoff time. Default: 50 seconds.

adjustShardHash

Boolean

Whether to adjust the shardHash on send. Default: true.

buckets

Integer

Effective when adjustShardHash is true. Regroups shardHash values into the specified number of buckets to improve batching.

Different shardHash values prevent data from being merged, which limits throughput. Regrouping enables more efficient batching.

Must be a power of 2 in the range [1, 256]. Default: 64.

Step 3: Create a producer

The Producer supports authentication with AccessKey pairs or STS tokens. For STS tokens, periodically create a new ProjectConfig and add it to ProjectConfigs.

LogProducer is the implementation class and requires a ProducerConfig instance. Create a Producer as follows:

Producer producer = new LogProducer(producerConfig);

Creating a Producer starts several threads, which is resource-intensive. Share a single Producer instance across your application. All LogProducer methods are thread-safe. The table below lists the internal threads, where N is the instance number starting at 0.

Thread name format

Quantity

Description

aliyun-log-producer-<N>-mover

1

Transfers batches ready to be sent to the sending thread pool.

aliyun-log-producer-<N>-io-thread

ioThreadCount

Threads in the IOThreadPool that execute data sending tasks.

aliyun-log-producer-<N>-success-batch-handler

1

Handles batches that have been successfully sent.

aliyun-log-producer-<N>-failure-batch-handler

1

Manages batches that failed to send.

Step 4: Configure a log project

ProjectConfig contains the endpoint and access credentials for a destination project. Each project requires its own ProjectConfig.

Create instances as follows:

ProjectConfig project1 = new ProjectConfig("your-project-1", "cn-hangzhou.log.aliyuncs.com", "accessKeyId", "accessKeySecret");
ProjectConfig project2 = new ProjectConfig("your-project-2", "cn-shanghai.log.aliyuncs.com", "accessKeyId", "accessKeySecret");
producer.putProject(project1);
producer.putProject(project2);

Step 5: Send data

Create Future or Callback

Specify a Callback when sending logs. The Callback is invoked on successful delivery or when an exception occurs during a failed send.

Note

If post-result processing is simple and non-blocking, use the Callback directly. Otherwise, use ListenableFuture to handle logic in a separate thread pool.

Method parameters:

Parameter

Description

project

The destination project for the data to be sent.

logstore

The destination logstore for the data to be sent.

logTem

The data to be sent.

completed

A Java atomic type to ensure that all logs are sent (both successfully and unsuccessfully).

Send data

The Producer interface provides multiple send methods with the following parameters:

Parameter

Description

Required

project

The destination project.

Yes

logStore

The destination logstore.

Yes

logItem

The logs to be sent.

Yes

topic

The topic of the logs.

No

Note

If not specified, this parameter is assigned "".

source

The source of the logs.

No

Note

If not specified, this parameter is assigned the IP address of the host where the producer resides.

shardHash

The hash value used to route logs to a specific shard in the Logstore.

No

Note

If not specified, data is written to a random shard.

callback

A Callback invoked on successful delivery or after all retries are exhausted.

No

Common exceptions

Exception

Description

TimeoutException

Thrown when the Producer's cached log size exceeds the memory limit and sufficient memory cannot be acquired within maxBlockMs.

If maxBlockMs is set to -1, blocking is indefinite and TimeoutException does not occur.

IllegalStateException

Thrown when the send method is called after the Producer has been closed.

Step 6: Obtain the sending result

The send method is asynchronous. Obtain the result through the returned Future or the provided Callback.

Future

The send method returns a ListenableFuture that supports callback registration. The example below registers a FutureCallback executed in a custom thread pool. Full example: SampleProducerWithFuture.java.

package com.aliyun.openservices.aliyun.log.producer.sample;

import com.aliyun.openservices.aliyun.log.producer.*;
import com.aliyun.openservices.aliyun.log.producer.errors.LogSizeTooLargeException;
import com.aliyun.openservices.aliyun.log.producer.errors.MaxBatchCountExceedException;
import com.aliyun.openservices.aliyun.log.producer.errors.ProducerException;
import com.aliyun.openservices.aliyun.log.producer.errors.ResultFailedException;
import com.aliyun.openservices.aliyun.log.producer.errors.TimeoutException;
import com.aliyun.openservices.log.common.LogItem;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SampleProducerWithFuture {

  private static final Logger LOGGER = LoggerFactory.getLogger(SampleProducerWithFuture.class);

  private static final ExecutorService EXECUTOR_SERVICE = Executors
      .newFixedThreadPool(Math.max(Runtime.getRuntime().availableProcessors(), 1));

  public static void main(String[] args) throws InterruptedException {
    Producer producer = Utils.createProducer();
    int n = 100;
    int size = 20;

    // The number of logs that have finished (either successfully send, or failed)
    final AtomicLong completed = new AtomicLong(0);

    for (int i = 0; i < n; ++i) {
      List<LogItem> logItems = Utils.generateLogItems(size);
      try {
        String project = System.getenv("PROJECT");
        String logStore = System.getenv("LOG_STORE");
        ListenableFuture<Result> f = producer.send(project, logStore, logItems);
        Futures.addCallback(
            f, new SampleFutureCallback(project, logStore, logItems, completed), EXECUTOR_SERVICE);
      } catch (InterruptedException e) {
        LOGGER.warn("The current thread has been interrupted during send logs.");
      } catch (Exception e) {
        if (e instanceof MaxBatchCountExceedException) {
          LOGGER.error("The logs exceeds the maximum batch count, e={}", e);
        } else if (e instanceof LogSizeTooLargeException) {
          LOGGER.error("The size of log is larger than the maximum allowable size, e={}", e);
        } else if (e instanceof TimeoutException) {
          LOGGER.error("The time taken for allocating memory for the logs has surpassed., e={}", e);
        } else {
          LOGGER.error("Failed to send logs, e=", e);
        }
      }
    }

    Utils.doSomething();

    try {
      producer.close();
    } catch (InterruptedException e) {
      LOGGER.warn("The current thread has been interrupted from close.");
    } catch (ProducerException e) {
      LOGGER.info("Failed to close producer, e=", e);
    }

    EXECUTOR_SERVICE.shutdown();
    while (!EXECUTOR_SERVICE.isTerminated()) {
      EXECUTOR_SERVICE.awaitTermination(100, TimeUnit.MILLISECONDS);
    }
    LOGGER.info("All log complete, completed={}", completed.get());
  }

  private static final class SampleFutureCallback implements FutureCallback<Result> {

    private static final Logger LOGGER = LoggerFactory.getLogger(SampleFutureCallback.class);

    private final String project;

    private final String logStore;

    private final List<LogItem> logItems;

    private final AtomicLong completed;

    SampleFutureCallback(
        String project, String logStore, List<LogItem> logItems, AtomicLong completed) {
      this.project = project;
      this.logStore = logStore;
      this.logItems = logItems;
      this.completed = completed;
    }

    @Override
    public void onSuccess(@Nullable Result result) {
      LOGGER.info("Send logs successfully.");
      completed.getAndIncrement();
    }

    @Override
    public void onFailure(Throwable t) {
      if (t instanceof ResultFailedException) {
        Result result = ((ResultFailedException) t).getResult();
        LOGGER.error(
            "Failed to send logs, project={}, logStore={}, result={}", project, logStore, result);
      } else {
        LOGGER.error("Failed to send log, e=", t);
      }
      completed.getAndIncrement();
    }
  }
}

Callback

The Callback runs on the Producer's internal thread, and data is only freed after it completes. Avoid long-running operations in the Callback to prevent blocking. Do not call the send method for retries within the Callback — use ListenableFuture instead. Full example: SampleProducerWithCallback.java.

package com.aliyun.openservices.aliyun.log.producer.sample;

import com.aliyun.openservices.aliyun.log.producer.Callback;
import com.aliyun.openservices.aliyun.log.producer.Producer;
import com.aliyun.openservices.aliyun.log.producer.Result;
import com.aliyun.openservices.aliyun.log.producer.errors.LogSizeTooLargeException;
import com.aliyun.openservices.aliyun.log.producer.errors.ProducerException;
import com.aliyun.openservices.aliyun.log.producer.errors.TimeoutException;
import com.aliyun.openservices.log.common.LogItem;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SampleProducerWithCallback {

  private static final Logger LOGGER = LoggerFactory.getLogger(SampleProducerWithCallback.class);

  private static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(10);

  public static void main(String[] args) throws InterruptedException {
    final Producer producer = Utils.createProducer();

    int nTask = 100;

    // The monotonically increasing sequence number we will put in the data of each log
    final AtomicLong sequenceNumber = new AtomicLong(0);

    // The number of logs that have finished (either successfully send, or failed)
    final AtomicLong completed = new AtomicLong(0);

    final CountDownLatch latch = new CountDownLatch(nTask);

    for (int i = 0; i < nTask; ++i) {
      EXECUTOR_SERVICE.submit(
          new Runnable() {
            @Override
            public void run() {
              LogItem logItem = Utils.generateLogItem(sequenceNumber.getAndIncrement());
              try {
                String project = System.getenv("PROJECT");
                String logStore = System.getenv("LOG_STORE");
                producer.send(
                    project,
                    logStore,
                    Utils.getTopic(),
                    Utils.getSource(),
                    logItem,
                    new SampleCallback(project, logStore, logItem, completed));
              } catch (InterruptedException e) {
                LOGGER.warn("The current thread has been interrupted during send logs.");
              } catch (Exception e) {
                if (e instanceof LogSizeTooLargeException) {
                  LOGGER.error(
                      "The size of log is larger than the maximum allowable size, e={}", e);
                } else if (e instanceof TimeoutException) {
                  LOGGER.error(
                      "The time taken for allocating memory for the logs has surpassed., e={}", e);
                } else {
                  LOGGER.error("Failed to send log, logItem={}, e=", logItem, e);
                }
              } finally {
                latch.countDown();
              }
            }
          });
    }
    latch.await();
    EXECUTOR_SERVICE.shutdown();

    Utils.doSomething();

    try {
      producer.close();
    } catch (InterruptedException e) {
      LOGGER.warn("The current thread has been interrupted from close.");
    } catch (ProducerException e) {
      LOGGER.info("Failed to close producer, e=", e);
    }

    LOGGER.info("All log complete, completed={}", completed.get());
  }

  private static final class SampleCallback implements Callback {

    private static final Logger LOGGER = LoggerFactory.getLogger(SampleCallback.class);

    private final String project;

    private final String logStore;

    private final LogItem logItem;

    private final AtomicLong completed;

    SampleCallback(String project, String logStore, LogItem logItem, AtomicLong completed) {
      this.project = project;
      this.logStore = logStore;
      this.logItem = logItem;
      this.completed = completed;
    }

    @Override
    public void onCompletion(Result result) {
      try {
        if (result.isSuccessful()) {
          LOGGER.info("Send log successfully.");
        } else {
          LOGGER.error(
              "Failed to send log, project={}, logStore={}, logItem={}, result={}",
              project,
              logStore,
              logItem.ToJsonString(),
              result);
        }
      } finally {
        completed.getAndIncrement();
      }
    }
  }
}

Step 7: Close the producer

Close the Producer when it is no longer needed to ensure all cached data is processed. Two shutdown modes are available:

Safe shutdown

Recommended for most cases. The close() method waits for all cached data to be processed, threads to stop, callbacks to execute, and futures to complete before returning.

This method returns quickly if the callback is non-blocking. After closure, batches are processed immediately without retries.

Limited shutdown

Use close(long timeoutMs) for a quick return when callbacks may block. If the Producer is not fully closed after timeoutMs, an IllegalStateException is thrown, indicating potential data loss and unexecuted callbacks.

FAQ

Are there any limitations on the number of data write operations?

  • Read and write operations in SLS are subject to size limits. For more information, see Data read and write.

  • SLS resources — projects, Logstores, shards, LogtailConfig, machine groups, LogItem size, LogItem (Key) length, and LogItem (Value) length — are subject to limits. For more information, see Basic resource limits.

What do I do if no data is written to SLS?

If no data is written to SLS, troubleshoot as follows:

  1. Verify that the aliyun-log-producer, aliyun-log, and protobuf-java JAR versions match the installation documentation. Upgrade if necessary.

  2. The send method is asynchronous. Use a Callback or Future to determine the failure cause.

  3. If the Callback's onCompletion method is not called, ensure producer.close() is invoked before the program exits. Calling producer.close() flushes all cached data.

  4. The Producer uses SLF4J for logging. Configure a logging framework and enable DEBUG-level logging to check for errors.

  5. If the issue persists, submit a ticket.

References