Tinylog
Lightweight and simple Java logging framework. Achieves high performance with minimal configuration, characterized by small footprint. Optimized for developers who want to avoid complex configuration and resource-constrained environments.
GitHub Overview
tinylog-org/tinylog
tinylog is a lightweight logging framework for Java, Kotlin, Scala, and Android
Topics
Star History
Library
tinylog
Overview
tinylog is a lightweight logging framework for Java, Kotlin, Scala, and Android developed as a high-performance logging solution with zero external dependencies. Composed of just two JAR files (API and implementation) with a combined size of approximately 195KB, it achieves an ultra-lightweight design. The static logger class design eliminates the need to create logger instances in each class, providing a simple and intuitive API. Featuring exceptional performance for disabled log statements with minimal overhead, it is optimized for use in microservices, Android applications, and resource-constrained environments.
Details
tinylog 2025 edition maintains its solid position as a mature lightweight logging solution with over a decade of development experience. It supports operation in diverse environments including JVM, GraalVM, Android, native images, Java SE, Java EE, JPMS projects, and OSGi platforms. Provides comprehensive log output options through support for console, file, JSON, database, syslog server, and Android logcat output. Advanced features such as asynchronous logging, buffered output, and SLF4J bridges enable support for enterprise-level requirements.
Key Features
- Ultra-lightweight Design: Minimal footprint of ~195KB with zero external dependencies
- Static Logger API: Simple design requiring no logger instance creation
- High-performance Architecture: Exceptional performance for disabled logs
- Diverse Output Support: Console, file, JSON, database, syslog support
- Cross-platform: JVM, Android, GraalVM, native image support
- Asynchronous Logging: Asynchronous output support for high throughput
Pros and Cons
Pros
- Deployment simplification through lightweight design with zero external dependencies
- Intuitive and concise API experience through static logger design
- Exceptional high performance and minimal resource usage
- Flexible log management through rich output options
- Optimization for microservices and Android applications
- Easy integration into existing projects through SLF4J bridge
Cons
- Limited feature set compared to Log4j and Logback
- Lack of advanced configuration options for complex enterprise environments
- Limited community size and third-party integrations
- Constraints in structured logging and complex filtering features
- Learning cost due to deviation from existing Java logging standards
- Relatively insufficient debugging and troubleshooting information
Reference Pages
Code Examples
Installation and Setup
<!-- Maven - pom.xml -->
<dependencies>
<!-- tinylog API -->
<dependency>
<groupId>org.tinylog</groupId>
<artifactId>tinylog-api</artifactId>
<version>2.6.2</version>
</dependency>
<!-- tinylog implementation -->
<dependency>
<groupId>org.tinylog</groupId>
<artifactId>tinylog-impl</artifactId>
<version>2.6.2</version>
</dependency>
<!-- SLF4J integration (optional) -->
<dependency>
<groupId>org.tinylog</groupId>
<artifactId>slf4j-tinylog</artifactId>
<version>2.6.2</version>
</dependency>
</dependencies>
// Gradle - build.gradle
dependencies {
// tinylog API
implementation 'org.tinylog:tinylog-api:2.6.2'
// tinylog implementation
runtimeOnly 'org.tinylog:tinylog-impl:2.6.2'
// SLF4J integration (optional)
implementation 'org.tinylog:slf4j-tinylog:2.6.2'
}
// Basic operation verification
import org.tinylog.Logger;
public class TinylogExample {
public static void main(String[] args) {
Logger.info("tinylog is working correctly!");
Logger.debug("Debug message test");
}
}
Basic Logging Output
import org.tinylog.Logger;
public class BasicLoggingExample {
public static void main(String[] args) {
// Basic log output at each level
Logger.trace("Detailed trace information");
Logger.debug("Debug information");
Logger.info("General information message");
Logger.warn("Warning: potential issue");
Logger.error("Error: something went wrong");
// Parameterized logging (recommended)
String userName = "John Doe";
int userId = 12345;
String action = "login";
Logger.info("User {} (ID: {}) performed {}", userName, userId, action);
Logger.debug("Processing time: {}ms", System.currentTimeMillis());
// Exception logging
try {
int result = 10 / 0;
} catch (ArithmeticException ex) {
Logger.error(ex, "Calculation error occurred");
Logger.error("Error details: {}", ex.getMessage());
}
// Conditional logging (performance-focused)
if (Logger.isDebugEnabled()) {
String expensiveDebugInfo = generateExpensiveDebugInfo();
Logger.debug("Debug info: {}", expensiveDebugInfo);
}
// Multiple parameter formatting
String product = "Laptop";
double price = 1299.99;
int quantity = 2;
double total = price * quantity;
Logger.info("Order details: {} × {} = ${}", product, quantity, total);
// Object information logging
User user = new User("Jane Smith", "[email protected]");
Logger.info("User created: {}", user);
// Array/collection logging
String[] categories = {"Electronics", "Books", "Clothing"};
Logger.debug("Available categories: {}", (Object) categories);
java.util.List<String> items = java.util.Arrays.asList("item1", "item2", "item3");
Logger.info("Processing items: {}", items);
}
private static String generateExpensiveDebugInfo() {
// Simulate expensive computation
return "Detailed system state information";
}
static class User {
private String name;
private String email;
public User(String name, String email) {
this.name = name;
this.email = email;
}
@Override
public String toString() {
return String.format("User{name='%s', email='%s'}", name, email);
}
}
}
Log Level Configuration
# tinylog.properties - Basic configuration
# Root level setting
level = info
# Output format setting
writer = console
writer.format = {date: yyyy-MM-dd HH:mm:ss.SSS} [{thread}] {level}: {message}
# Package-specific level settings
[email protected] = debug
[email protected] = warn
[email protected] = error
# Multiple output destinations
writer1 = console
writer1.level = info
writer1.format = {date: HH:mm:ss} {level}: {message}
writer2 = file
writer2.level = debug
writer2.file = logs/application.log
writer2.format = {date: yyyy-MM-dd HH:mm:ss.SSS} [{thread}] {level} {class}.{method}() - {message}
# File rotation settings
writer3 = rolling file
writer3.level = info
writer3.file = logs/app-{date: yyyy-MM-dd}.log
writer3.policies = startup, daily: 00:00
writer3.format = {date: yyyy-MM-dd HH:mm:ss.SSS} {level}: {message}
// Programmatic level configuration
import org.tinylog.configuration.Configuration;
public class DynamicConfiguration {
public static void configureLogging() {
// Dynamically change log level
Configuration.set("level", "debug");
// Set levels for specific classes
Configuration.set("[email protected]", "trace");
Configuration.set("[email protected]", "warn");
// Dynamic output destination configuration
Configuration.set("writer", "console");
Configuration.set("writer.format", "{date: HH:mm:ss} {level}: {message}");
Logger.info("Log configuration updated dynamically");
}
public static void setupProductionLogging() {
// Production environment configuration
Configuration.set("level", "info");
Configuration.set("writer1", "file");
Configuration.set("writer1.file", "/var/log/myapp/application.log");
Configuration.set("writer1.format",
"{date: yyyy-MM-dd HH:mm:ss.SSS} [{thread}] {level} {class} - {message}");
// Separate error logging
Configuration.set("writer2", "file");
Configuration.set("writer2.file", "/var/log/myapp/error.log");
Configuration.set("writer2.level", "error");
Configuration.set("writer2.format",
"{date: yyyy-MM-dd HH:mm:ss.SSS} {level}: {message}{exception}");
Logger.info("Production log configuration completed");
}
}
Java-specific Features
import org.tinylog.Logger;
import org.tinylog.TaggedLogger;
public class JavaSpecificFeatures {
// Tagged logger usage
private static final TaggedLogger SECURITY_LOGGER = Logger.tag("SECURITY");
private static final TaggedLogger PERFORMANCE_LOGGER = Logger.tag("PERFORMANCE");
private static final TaggedLogger DATABASE_LOGGER = Logger.tag("DATABASE");
public static void demonstrateTaggedLogging() {
// Security-related logging
SECURITY_LOGGER.warn("Unauthorized login attempt from IP: {}", "192.168.1.100");
SECURITY_LOGGER.info("User authentication successful: {}", "user123");
// Performance-related logging
long startTime = System.currentTimeMillis();
performSomeOperation();
long duration = System.currentTimeMillis() - startTime;
PERFORMANCE_LOGGER.info("Operation completed in: {}ms", duration);
// Database-related logging
DATABASE_LOGGER.debug("SQL execution: SELECT * FROM users WHERE id = ?", 123);
DATABASE_LOGGER.error("Database connection error");
}
// Java 8+ stream operations with logging
public static void streamLoggingExample() {
java.util.List<String> items = java.util.Arrays.asList(
"item1", "item2", "item3", "item4"
);
Logger.info("Processing started: {} items", items.size());
java.util.List<String> processed = items.stream()
.peek(item -> Logger.debug("Processing: {}", item))
.map(String::toLowerCase)
.peek(item -> Logger.trace("Transformed: {}", item))
.filter(item -> item.length() > 2)
.peek(item -> Logger.debug("Filter passed: {}", item))
.collect(java.util.stream.Collectors.toList());
Logger.info("Processing completed: {} results", processed.size());
}
// CompletableFuture and asynchronous logging
public static void asyncLoggingExample() {
Logger.info("Async processing started");
java.util.concurrent.CompletableFuture<String> future =
java.util.concurrent.CompletableFuture.supplyAsync(() -> {
Logger.debug("Async task executing...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Logger.warn("Sleep interrupted", e);
Thread.currentThread().interrupt();
}
return "async result";
});
future.thenAccept(result -> {
Logger.info("Async processing completed: {}", result);
}).exceptionally(throwable -> {
Logger.error(throwable, "Async processing error");
return null;
});
}
// Resource management with logging
public static void resourceManagementLogging() {
Logger.debug("Resource acquisition started");
try (java.io.FileInputStream fis = new java.io.FileInputStream("example.txt")) {
Logger.info("File opened successfully");
// File processing
Logger.debug("File processing completed");
} catch (java.io.IOException e) {
Logger.error(e, "File processing error: {}", e.getMessage());
} finally {
Logger.debug("Resource cleanup completed");
}
}
// Custom formatting with logging
public static void customFormattingExample() {
// JSON-style format
Logger.info("{{\"event\": \"user_login\", \"user_id\": {}, \"timestamp\": {}}}",
12345, System.currentTimeMillis());
// CSV-style format
Logger.info("USER_ACTION,{},{},{}",
"user123", "CREATE_POST", java.time.LocalDateTime.now());
// Structured information logging
logStructuredEvent("ORDER_CREATED",
"order_id", "ORD-001",
"customer_id", "CUST-456",
"amount", 1299.99);
}
private static void logStructuredEvent(String event, Object... keyValues) {
StringBuilder sb = new StringBuilder();
sb.append("EVENT:").append(event);
for (int i = 0; i < keyValues.length; i += 2) {
if (i + 1 < keyValues.length) {
sb.append(" ").append(keyValues[i]).append("=").append(keyValues[i + 1]);
}
}
Logger.info(sb.toString());
}
private static void performSomeOperation() {
// Simulate some operation
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
Configuration File Examples
# tinylog.properties - Complete configuration example
# Global level setting
level = info
# === Console output configuration ===
writer1 = console
writer1.level = debug
writer1.stream = out
writer1.format = {date: HH:mm:ss.SSS} [{thread}] {level}: {class-name}.{method}() - {message}
# === Main file output configuration ===
writer2 = file
writer2.level = info
writer2.file = logs/application.log
writer2.charset = UTF-8
writer2.buffered = true
writer2.format = {date: yyyy-MM-dd HH:mm:ss.SSS} [{thread}] {level} {class} - {message}{exception}
# === Error file separation configuration ===
writer3 = file
writer3.level = error
writer3.file = logs/error.log
writer3.format = {date: yyyy-MM-dd HH:mm:ss.SSS} {level}: {message}{exception: |}
# === Rolling file configuration ===
writer4 = rolling file
writer4.level = debug
writer4.file = logs/debug-{date: yyyy-MM-dd}.log
writer4.policies = startup, daily: 00:00, size: 10MB
writer4.backups = 7
writer4.format = {date: HH:mm:ss.SSS} {level} {class}.{method}():{line} - {message}
# === JSON output configuration ===
writer5 = json file
writer5.level = info
writer5.file = logs/application.json
writer5.field.timestamp = {date: yyyy-MM-dd'T'HH:mm:ss.SSSXXX}
writer5.field.level = {level}
writer5.field.thread = {thread}
writer5.field.logger = {class}
writer5.field.message = {message}
writer5.field.exception = {exception}
# === Package-specific level configuration ===
[email protected] = debug
[email protected] = info
[email protected] = warn
# Third-party libraries
[email protected] = warn
[email protected] = error
[email protected] = info
# === Tag-specific configuration ===
tag@SECURITY = SECURITY
level@tag:SECURITY = info
writer@tag:SECURITY = file
writer@tag:SECURITY.file = logs/security.log
writer@tag:SECURITY.format = {date: yyyy-MM-dd HH:mm:ss} SECURITY {message}
tag@PERFORMANCE = PERFORMANCE
level@tag:PERFORMANCE = debug
writer@tag:PERFORMANCE = file
writer@tag:PERFORMANCE.file = logs/performance.log
# tinylog.yaml - YAML configuration example
level: info
writers:
- type: console
level: debug
format: "{date: HH:mm:ss.SSS} [{thread}] {level}: {message}"
- type: file
level: info
file: "logs/application.log"
charset: "UTF-8"
buffered: true
format: "{date: yyyy-MM-dd HH:mm:ss.SSS} [{thread}] {level} {class} - {message}{exception}"
- type: rolling file
level: debug
file: "logs/app-{date: yyyy-MM-dd}.log"
policies:
- type: startup
- type: daily
time: "00:00"
- type: size
size: "50MB"
backups: 30
format: "{date: yyyy-MM-dd HH:mm:ss.SSS} {level} {class}.{method}():{line} - {message}"
# Package level configuration
levels:
"com.example.service": debug
"com.example.database": warn
"org.springframework": error
Performance Optimization
import org.tinylog.Logger;
import org.tinylog.configuration.Configuration;
public class PerformanceOptimization {
// Batch processing for high-frequency logging
public static void batchLoggingExample() {
// Enable asynchronous writing
Configuration.set("writingthread", "true");
Configuration.set("writingthread.priority", "1"); // Lowest priority
Logger.info("Asynchronous log writing started");
// Mass log output test
long startTime = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
Logger.debug("Batch processing #{}: Processing data...", i);
}
long duration = System.currentTimeMillis() - startTime;
Logger.info("10,000 log output completed: {}ms", duration);
}
// Buffering optimization
public static void bufferingOptimization() {
// Buffer size configuration
Configuration.set("writer", "file");
Configuration.set("writer.file", "logs/buffered.log");
Configuration.set("writer.buffered", "true");
Configuration.set("writer.buffering.size", "65536"); // 64KB buffer
Logger.info("Buffering optimization configuration completed");
// Buffer effect test
long startTime = System.nanoTime();
for (int i = 0; i < 1000; i++) {
Logger.info("Buffering test #{}: {}", i, generateTestData());
}
long duration = (System.nanoTime() - startTime) / 1_000_000;
Logger.info("Buffering test completed: {}ms", duration);
}
// Conditional logging optimization
public static void conditionalLoggingOptimization() {
// Level check optimization
if (Logger.isTraceEnabled()) {
String expensiveTrace = performExpensiveCalculation();
Logger.trace("Detailed trace: {}", expensiveTrace);
}
if (Logger.isDebugEnabled()) {
java.util.Map<String, Object> debugInfo = gatherDebugInformation();
Logger.debug("Debug information: {}", debugInfo);
}
// Optimization without level check (tinylog auto-optimizes)
Logger.trace("Auto-optimization: {}", performExpensiveCalculation());
Logger.debug("Auto-optimization debug: {}", gatherDebugInformation());
}
// Memory efficiency optimization
public static void memoryOptimization() {
// Avoid string concatenation to reduce garbage collection load
Logger.info("User info: ID={}, name={}, email={}",
getUserId(), getUserName(), getUserEmail());
// Avoid StringBuilder usage (use parameters)
int items = 100;
double total = 1560.50;
Logger.info("Order summary: {} items, total ${}", items, total);
// Minimize object creation
logUserAction("LOGIN", System.currentTimeMillis(), true);
}
// Asynchronous logging configuration
public static void asyncLoggingSetup() {
// Asynchronous writing thread configuration
Configuration.set("writingthread", "true");
Configuration.set("writingthread.priority", "3"); // Lower than normal priority
Configuration.set("writingthread.observe", "5s"); // Check every 5 seconds
Logger.info("Asynchronous log configuration completed");
// High throughput test
java.util.concurrent.ExecutorService executor =
java.util.concurrent.Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
final int threadId = i;
executor.submit(() -> {
for (int j = 0; j < 1000; j++) {
Logger.info("Thread {} - Message {}", threadId, j);
}
});
}
executor.shutdown();
try {
if (executor.awaitTermination(30, java.util.concurrent.TimeUnit.SECONDS)) {
Logger.info("All threads completed");
} else {
Logger.warn("Timeout: Some threads incomplete");
}
} catch (InterruptedException e) {
Logger.error("Wait interrupted", e);
Thread.currentThread().interrupt();
}
}
// Custom writer optimization
public static void customWriterOptimization() {
// Multiple writer efficiency
Configuration.set("writer1", "console");
Configuration.set("writer1.level", "warn");
Configuration.set("writer1.format", "{level}: {message}");
Configuration.set("writer2", "file");
Configuration.set("writer2.level", "debug");
Configuration.set("writer2.file", "logs/detailed.log");
Configuration.set("writer2.buffered", "true");
Configuration.set("writer2.format",
"{date: yyyy-MM-dd HH:mm:ss.SSS} {level} {class}.{method}() - {message}");
Configuration.set("writer3", "rolling file");
Configuration.set("writer3.level", "info");
Configuration.set("writer3.file", "logs/production-{date: yyyy-MM-dd}.log");
Configuration.set("writer3.policies", "daily: 00:00, size: 100MB");
Configuration.set("writer3.backups", "30");
Logger.info("Multi-writer configuration completed");
}
// Helper methods
private static String performExpensiveCalculation() {
// Simulate expensive computation
return "Calculation result: " + Math.random();
}
private static java.util.Map<String, Object> gatherDebugInformation() {
java.util.Map<String, Object> info = new java.util.HashMap<>();
info.put("memory", Runtime.getRuntime().totalMemory());
info.put("threads", Thread.activeCount());
info.put("timestamp", System.currentTimeMillis());
return info;
}
private static String generateTestData() {
return "TestData_" + System.nanoTime();
}
private static void logUserAction(String action, long timestamp, boolean success) {
Logger.info("User action: {} at {} (success: {})", action, timestamp, success);
}
private static int getUserId() { return 12345; }
private static String getUserName() { return "John Doe"; }
private static String getUserEmail() { return "[email protected]"; }
}