Skip to content

Fluent Bit

Este colector de registros (logs) código abierto es ligero y eficiente, con opciones variadas para tomar archivos de salida y llevarlos a una fuente de datos. Es una alternativa a FluentD (de los mismos autores)

Instalando Fluent Bit

Fluent Bit se puede descargar para Linux o macOS con el siguiente comando:

bash
curl https://raw.githubusercontent.com/fluent/fluent-bit/master/install.sh | sh

Para instalarlo en Ubuntu se puede ejecutar:

bash
sudo apt update && sudo apt install fluent-bit

Configurando Fluent Bit con YAML

Para configurar Fluent Bit usando la versión de YAML (convencionalmente se usa TOML), podemos establecer un archivo fluent-bit.yaml con un contenido como el siguiente:

yaml
service:
  flush: 1
  log_level: info
  parsers_file: parsers.yaml

pipeline:
  inputs:
    - name: tail
      tag: springboot_logs
      path: ./logs/application.log
      read_from_head: true
      multiline.parser: springboot_multiline
  outputs:
    - name: stdout
      match: *

Primero se define el servicio y luego una entrada tail, finalmente la salida a stdout (u otro destino).

Para complementar la configuración con el archivo parsers.yaml podemos tener un contenido como el siguiente:

yaml
parsers:
  - name: springboot_parser
    format: regex
    regex: '^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}) \[([^\]]*)\] (\w+) +(\S+) - (.*)'
    time_key: time
    time_format: '%Y-%m-%d %H:%M:%S.%L'
    time_keep: true

multiline_parsers:
  - name: springboot_multiline
    type: regex
    flush_timeout: 1000
    rules:
      - state: start_state
        regex: '/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}).*/'
        next_state: cont
      - state: cont
        regex: '/^\s+at.*/'
        next_state: cont

La expresion regex captura logs formateando los datos respectivamente.
springboot_multiline es usado cuando en Java se generan errores en pila (Stack Traces).

Ejemplo usando programa Java con JBang

Para ver en acción la magia de Fluent Bit, consideremos un ejemplo esencial en Java y SpringBoot, usando JBang para agilizar y simplificar el ejercicio. Definimos la clase LoggerApp.java con el siguiente contenido:

java
//usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS org.springframework.boot:spring-boot-starter-web:3.3.2

package logapp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@SpringBootApplication
public class LoggerApp {

    public static void main(String[] args) {
        SpringApplication.run(LoggerApp.class, args);
    }

    @RestController
    static class LogController {
        private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");

        @GetMapping("/log")
        public String generateLog(@RequestParam(defaultValue = "test") String message) {
            String timestamp = LocalDateTime.now().format(formatter);
            System.out.printf("%s [http-thread] INFO  LoggerApp$LogController - Request with message: %s%n", timestamp, message);
            try {
                if (message.equals("error")) {
                    throw new RuntimeException("Simulated error for testing");
                }
            } catch (Exception e) {
                System.err.printf("%s [http-thread] ERROR LoggerApp$LogController - Error in request%n", timestamp);
                e.printStackTrace();
            }
            return "Log generated: " + message;
        }
    }
}

Para iniciar el programa anterior ejecutamos:

bash
jbang LoggerApp.java > logs/application.log

En otra terminal iniciamos el servicio de Fluent Bit así:

bash
fluent-bit -c fluent-bit.yaml

Luego, podemos hacer una petición HTTP para generar un log:

bash
curl http://localhost:8080/log

Si se desea generar un error intenta con: curl http://localhost:8080/log\?message=error

Fluent Bit usando un contonedor Docker

Para continuar nuestro ejercicio usando Docker definiremos un Dockerfile con un contenido como el siguiente:

dockerfile
FROM openjdk:17-jdk-slim
COPY LoggingAppStdout.java .
RUN curl -Ls https://sh.jbang.dev | bash -s - app install
EXPOSE 8080
CMD ["jbang", "run", "LoggerApp.java"]

A partir de una imagen con Java se copia la clase usada anteriormente y se instala JBang.

Para usar la salida de Docker con Fluent Bit usamos, configuramos fluent-bit.yaml con el siguiente contenido:

yaml
service:
  flush: 1
  log_level: info
  parsers_file: parsers.yaml

pipeline:
  inputs:
    - name: docker
      tag: springboot_logs
  outputs:
    - name: stdout
      match: *

En este caso usamos la entrada docker que permite leer los logs de los contenedores. También podría usarse containerd

Para usar nuestra aplicación y FuentBit juntos, definimos un archivo docker-compose.yml con un contenido como el siguiente:

yaml
version: '3.8'
services:
  logger-app:
    build: .
    ports:
      - "8080:8080"
    volumes:
      - ./logs:/app/logs
    networks:
      - logging-network

  fluent-bit:
    image: fluent/fluent-bit:latest
    volumes:
      - ./fluent-bit.yaml:/fluent-bit/etc/fluent-bit.yaml
      - ./parsers.yaml:/fluent-bit/etc/parsers.yaml
    networks:
      - logging-network

Aquí definimos dos servicios: logger-app para nuestra aplicación Java y fluent-bit para el colector de logs. Ambos comparten una red llamada logging-network.

Para iniciar los servicios, ejecutamos:

bash
docker-compose up --build

Una vez que los servicios estén en ejecución, podemos generar logs haciendo peticiones HTTP a nuestra aplicación:

bash
curl http://localhost:8080/log

Si se desea generar un error intenta con: curl http://localhost:8080/log?message=error

Con este ejercicio hemos visto cómo integrar Fluent Bit con una aplicación Java simplificada, permitiendo la recolección y visualización de logs de manera eficiente. Esta configuración es ideal para entornos de desarrollo y producción, facilitando el monitoreo y la depuración de aplicaciones.

Ejemplo de DaemonSet para FluentBit en Kubernetes

Si piensas en Kubernetes encuentras que se usa FluentBit con DaemonSet de un modo eficiente. Como un abrebocas dejo un ejemplo para ilustrar brevemente el manifiesto:

yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluent-bit
  namespace: logging
spec:
  selector:
    matchLabels:
      name: fluent-bit
  template:
    metadata:
      labels:
        name: fluent-bit
    spec:
      containers:
      - name: fluent-bit
        image: fluent/fluent-bit:latest
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
        - name: fluent-bit-config
          mountPath: /fluent-bit/etc/
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
      - name: fluent-bit-config
        configMap:
          name: fluent-bit-config
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluent-bit-config
  namespace: logging
data:
  fluent-bit.conf: |
    [SERVICE]
        Flush         1
        Log_Level     info
        Daemon        off
        Parsers_File  parsers.conf
    [INPUT]
        Name              tail
        Path              /var/log/containers/*.log
        Parser            docker
        Tag               kube.*
        Refresh_Interval  5
    [OUTPUT]
        Name  stdout
        Match *

Este manifiesto con DaemonSet despliega Fluent Bit en cada nodo del clúster, recolectando logs de todos los contenedores y enviándolos a la salida configurada.
Nótese que para la configuración de Fluent Bit se usa en este ejemlo TOML.