domingo, 17 de junio de 2018

ELK - Gestión de logs en cloud (y II)

Veamos ahora el "otro lado", el lado de aplicación.

Veremos en esta entrada como enviar los logs de una aplicación Springboot hacia nuestro ELK configurado y arrancado según la entrada anterior.

Como siempre, el código completo lo podéis descargar de  nuestro gitlab.

Comencemos.


pom.xml

Mostramos aquí sólo la configuración específica. Para verlo en su extensión descargad el código del gitlab y veréis la versión completa.

Esto es lo que necesitamos:

    <properties>
    <logstash.version>5.1</logstash.version>
    </properties>


    <dependency>
    <groupId>net.logstash.logback</groupId>
    <artifactId>logstash-logback-encoder</artifactId>
    <version>${logstash.version}</version>
    </dependency>


Java

La aplicación que vamos a crear la llamaremos "elk-example".

En este ejemplo no usaremos el config-server que mostramos en varias entradas anteriores. Si queremos hacerlo bastaría con seguir los diferentes artículos, comenzando por el primero de ellos.

La configuración de envío de logs a ELK se podría hacer vía fichero xml, no obstante, en nuestro caso, hemos optado por hacerlo programáticamente. 

Hemos usado una clase de configuratión como la siguiente:

@Component
@ConditionalOnProperty(name = "logstash.enabled", havingValue = "true", matchIfMissing = false)
public class LogstashConfiguration {

private LogstashTcpSocketAppender logstashTcpSocketAppender;

@Value("${logstash.server.host}")
String logstashServerHost;
@Value("${logstash.server.port}")
String logstashServerPort;
@PostConstruct
    public void init() {
        Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
        LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
        LogstashEncoder encoder = new LogstashEncoder();
        
        logstashTcpSocketAppender = new LogstashTcpSocketAppender();
        logstashTcpSocketAppender.setName("logstash");
        logstashTcpSocketAppender.setContext(context);
        logstashTcpSocketAppender.addDestination(logstashServerHost+":"+logstashServerPort);
        
        logstashTcpSocketAppender.setEncoder(encoder);
        logstashTcpSocketAppender.start();

        rootLogger.addAppender(logstashTcpSocketAppender);
        rootLogger.setLevel(Level.INFO);
        
}
}

Hemos añadido la anotación "@ConditionalOnProperty" para que podamos desactivar esta configuración según deseemos.

Vemos que necesita de dos variables capturadas vía "@Value". Por tanto, si activamos ELK esas variables deberán contener un valor en los properties de la aplicación. Más adelante lo veremos.

Además, queremos que nuestra aplicación se diferencie de otras que puedan enviar sus logs al mismo ELK. Es por ello que mediante un filter vamos a setear un atributo y anexarlo a todos nuestros logs.

El filter es este:

@Component
public class RequestFilter implements Filter {

@Value("${spring.application.name}")
private String applicationName;
@Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            try {
                MDC.put(Constants.SERVICE_NAME_HEADER_KEY, applicationName);
                chain.doFilter(request, response);
            } finally {
               MDC.clear();
            }
}

@Override
public void init(FilterConfig filterConfig) throws ServletException {
}

@Override
public void destroy() {
}
}

El atributo que añdimos es el "servicename" asociado a la constante "SERVICE_NAME_HEADER_KEY". Esta variable contendrá el nombre de nuestra aplicación.

Crearemos una clase RestController con un sólo método, dentro del cual se escribirá un pequeño log de muestra:

@RestController
public class MainController {

private static final Logger log = LoggerFactory.getLogger(MainController.class);
@GetMapping(path = "/whoami/{username}")
public String whoami(@PathVariable("username") String username) {
log.info("acceso con username: " + username);
return String.format("Hola! %s", username);
}
}

La url que utilizaremos para invocar el servicio será "http://localhost:puerto/whoami/nombre".

Nos faltará ver como se acaba ubicando en nuestros logs cuando veamos el application.yml.


application.yml

Veamos ahora el contenido específico de este fichero

    .....
    spring:
      application:
        name: elk-example
      profiles:
        active: des
    logstash:
      enabled: true
      server:
        host: 192.168.99.100
        port: 5044
    
    logging:
      pattern:
        level: 'servicename=%X{servicename:-} %5p'
    ....

El nombre de la aplicación, y que se añadirá a los logs será "elk-example"
El profile activo será "des".

Habilitamos logstash (recordad la anotación "@ConditionalOnProperty" descrita más arriba)
Informamos el host y el puerto de Logstash dentro de ELK (ver anterior entrada).

En el log se añade "servicename=%X{servicename:-}", cuyo valor viene informado desde el filter comentado anteriormente.

Alguien puede decir, de dónde sale la ip asignada al host. Bien, se trata de la ip de nuestra docker-machine (arq-root) que levantamos según la anterior entrada. Si queremos validarlo antes deberemos ejecutar el siguiente comando:

  docker-machine ip arq-root

Habitualmente, en un entorno windows "normal" suele ser "192.168.99.100". Si tenemos varias docker-machine arrancadas a la vez, las ip's asignadas suelen ser correlativas, comenzando por la comentada "192.168.99.100". 


logback-spring.xml

Este fichero no contendrá nada, al menos en este ejemplo base. Únicamente lo siguiente:

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>

    <include
        resource="org/springframework/boot/logging/logback/base.xml" />

    </configuration>

Bueno, ya lo tenemos todo.


Prueba

Como podéis ver, los requerimientos son realmente livianos. La aplicación escribe sus logs como siempre, unicamente hemos añadido una configuración de forma programática en una clase de configuración, e indicado un host y puerto de envío de logs.

Podríamos empaquetar la aplicación y desplegarla en un container docker, pero en este ejemplo lanzaremos nuestra aplicación desde eclipse, tal como haría un desarrollador en su entorno local.

Una vez arrancada invocamos la url del servicio expuesto, que en nuestro caso queda como

    http://localhost:8282/whoami/antonio


Ahora accedemos a los índices de elasticsearch "http://192.168.99.100:9200/_cat/indices" y vemos un nuevo índice, "micro-elk-example".

recordad el contenido del fichero "02-beats-input.conf" de la entrada anterior:
    index => "micro-%{servicename}"

añadid a esto el contenido del filter
    MDC.put(Constants.SERVICE_NAME_HEADER_KEY, applicationName);

donde "applicationName" era el nombre de la aplicación.

Por tanto, todo ello reunido genera un índice con nombre "micro-elk-example". Si otra aplicación llamada, por ejemplo, "elk-otro-example" siguiese ese mismo patrón, al final generaría un nuevo index con nombre "micro-elk-otro-example". Este patrón nos irá bien a la ora de explotar los logs en Kibana.

Veamoslo


Kibana

Accedemos a Kibana "http://192.168.99.100:5601/" y añadimos un patrón:

ponemos el específico de nuestra aplicación "micro-elk-example" para sólo ver sus logs. Si quisiésemos ver los logs de varias aplicaciones con el mismo patrón, podríamos crear otro con valor "micro-*".


Ahora ya podemos ver los logs generados por nuestra petición 


podemos jugar un poco con los filtros para ver sólo los campos que queremos:


 Bueno, atractivo, no?

De momento aquí lo dejamos. Es sólo una muestra de las posibilidades de visualización de logs en un entorno cloud, donde habrá cientos de contenedores que se levantan aquí y allá, etc...

En este caso vemos que incluso en un entorno local sin aplicaciones corriendo en contenedores también se puede utilizar un ELK. Seguramente será esta forma necesaria como paso previo al despliegue de cualquier aplicación en un entorno cloud posterior.

Como digo, lo que quería mostrar es que este tipo de gestión de logs puede ser usado de diferentes formas, en un entorno cloud (más propio) pero también en un entorno de desarrollo típico.


No hay comentarios:

Publicar un comentario