Dic 192015
 

En este artículo se explica el procedimiento para cargar en un índice solr un fichero que contiene un conjunto de documentos en formato XML. Para ello utilizaremos la utilidad Logstash, que forma parte del paquete SiLK de análisis de ficheros de log.

Instalación del paquete SiLK

SiLK es un paquete que contiene la aplicación Logstash preparada para importar documentos en una colección solr.

Para realizar la instalación de SiLK, se pueden seguir seguir las instrucciones del artículo “Cómo analizar los logs de acceso de un servidor web con SiLK” de este mismo blog.

Preparación del fichero de entrada

Logstash procesa un fichero de texto que consta de una serie de documentos, con la condición de que cada línea del fichero contenga un documento completo. Por ejemplo, supongamos que queremos importar documentos XML con la estructura:

<doc>
    <id>...</id>
    <nombre>...</nombre>
    <fecha>...</fecha>
   ...
</doc>

Para importarlos, el fichero de entrada debe estar preparado de forma que cada documento ocupe una línea, como en el siguiente ejemplo:

<doc><id>0001</id><nombre>...</nombre><fecha>...</fecha>...</doc>
<doc><id>0002</id><nombre>...</nombre><fecha>...</fecha>...</doc>
..

A menudo, este no es el caso. Por ejemplo, es frecuente que el fichero XML que contiene todos los documentos no contenga ningún salto de línea. Si se trata de un fichero de gran tamaño, es también frecuente que esté comprimido para ocupar menos espacio de disco. En estos casos, es necesario utilizar algún tipo de preproceso para convertir el fichero de modo que cada documento esté contenido en una línea.

Por ejemplo, el siguiente script “split_docs.php” lee de standard input un conjunto de documentos “<doc>…</doc>”, y escribe en standard output un documento por línea:

<?php
    do {
        $data=fgets(STDIN,1024);
        $data=str_replace("<doc>","\n<doc>", $data);
        $data=str_replace("</doc><","</doc>\n<", $data);
        print $data;
    } while($data!='');

Si tenemos un fichero “datos.xml.gz” comprimido en GZIP, que contiene un conjunto de documentos <doc>…</doc> sin saltos de línea, podemos descomprimirlo, convertirlo a un documento por línea, y filtrar las líneas que no contengan documentos, con un comando de la forma:

$ zcat datos.xml.gz | php split_docs.php | grep "^<doc>"

Preparación del fichero de configuración de Logstash

En primer lugar, preparamos un fichero “importacion_xml.conf” especificando el tipo de proceso que logstash debe realizar.

El fichero de configuración de logstash consta de las secciones “input”, “filter” y “output”. Para ilustrar este artículo, vamos a configurar una sección “input” que simplemente lee los datos a procesar de standard input:

input {
  stdin {
  }
}

En la sección “filter” se indica el tipo de proceso deseado. En esta sección, vamos a incluir un módulo “xml” que realiza la conversión del formato xml recibido a una estructura de datos que se almacena en el campo “entry” del documento de salida:

filter {
    xml {
        source => message
        target => entry
    }
    # Eliminar el campo "message", que contiene el mensaje original en formato XML.
    mutate {
        remove_field => ["message"]
    }
}

Por último, en la sección “output” indicamos el tipo de salida deseada. Inicialmente, especificamos que la salida sea por standard output, para poder depurar la configuración. Más adelante, utilizaremos la salida “lucidwords_solr_lsv133” que aparece comentada en el ejemplo, para que los documentos procesados sean insertados en la colección solr:

output {

  stdout { debug => true codec => "rubydebug"}

  #lucidworks_solr_lsv133 { collection_host => "localhost" collection_port => "8983" 
  #      collection_name => "datos_xml" field_prefix => "event_"
  #      force_commit => false flush_size => 100 idle_flush_time => 1 }

}

Ejecución de Logstash en modo debug

Con esto, podemos realizar una primera ejecución de logstash.

Preparamos un fichero “datos.xml” con un único documento de prueba:

<ad>
    <id><![CDATA[0016587830]]></id>
    <date><![CDATA[2015-12-15 12:00:04]]></date>
    <region><![CDATA[salerno]]></region>
    <city><![CDATA[scafati]]></city>
    <title><![CDATA[Appartamento in affitto ristrutturato con box auto]]></title>
    <pictures>
        <picture>
            <picture_url><![CDATA[http://cdn.mydomain.it/0054284101G.jpg]]></picture_url>
        </picture>
    </pictures>
    <type><![CDATA[For Rent]]></type>
    <price currency="EUR"><![CDATA[450]]></price>
</ad>

Nota: Como hemos comentado previamente, el documento debe estar en una única línea en el fichero de entrada, aunque aquí se presenta en varias lineas para facilitar su lectura.

Y lanzamos la ejecución de logstash:

$ cd SiLK-1.5/solrWriterForLogStash/logstash_deploy
$ java -jar logstash-1.3.3-flatjar.jar agent -f importacion_xml.conf -p . < datos.xml

Con esto, logstash imprime en standard output, en formato JSON, el documento que resulta de la conversión del documento XML contenido en el fichero de entrada:

# java -jar logstash-1.3.3-flatjar.jar agent -f importacion_xml.conf -p . < datos.xml
Using milestone 1 filter plugin 'xml'. This plugin should work, but would benefit from use by folks
like you. Please let us know if you find bugs or have suggestions on how to improve this plugin.
For more information on plugin milestones, see
http://logstash.net/docs/1.3.3/plugin-milestones {:level=>:warn}
{
      "@version" => "1",
    "@timestamp" => "2015-12-19T09:24:23.200Z",
         "host" => "squeeze",
         "entry" => {
               "id" => [
            [0] "00634923058"
        ],
              "url" => [
            [0] "http://www.mydomain.it/"
        ],
             "date" => [
            [0] "2015-12-15 12:00:04"
        ],
           "region" => [
            [0] "salerno"
        ],
             "city" => [
            [0] "scafati"
        ],
            "title" => [
            [0] "Appartamento in affitto ristrutturato con box auto"
        ],
         "pictures" => [
            [0] {
                "picture" => [
                    [0] {
                        "picture_url" => [
                            [0] "http://cdn.mydomain.it/00634923058G.jpg"
                        ]
                    }
                ]
            }
        ],
             "type" => [
            [0] "For Rent"
        ],
            "price" => [
            [0] {
                "currency" => "EUR",
                 "content" => "450"
            }
        ]
    }
}

En la salida generada, podemos observar que:

  • El contenido del documento XML de origen se almacena en el campo “entry” del documento JSON resultante. Además, logstash añade al documento JSON otros campos “@version”, “@timestamp” y “host”.
  • La estructura jerárquica del documento XML se conserva en el formato JSON. Los valores de los campos de entrada se transforman en arrays de valores en los campos de salida.
  • El campo “price” en el documento XML contiene un atributo “currency”:
<price currency="EUR"><![CDATA[450]]></price>
  • En el documento JSON, el atributo “currency” y el contenido del campo son convertidos a los campos “currency” y “content” de un array asociativo:
"price" => [
            [0] {
                "currency" => "EUR",
                 "content" => "450"
            }
        ]

Eliminar campos y niveles innecesarios

Podemos simplificar la estructura del documento de salida y eliminar niveles de jerarquía innecesarios, añadiendo algunas directrices a la sección “filter” del fichero de configuración de logstash.

Por ejemplo, para mantener únicamente los campos “id”, “date”, “picture_url” y “price”, podemos utilizar la siguiente configuración:

    xml {
        source => message
        target => entry
        add_field => {
            id         => "%{[entry][id]}"
            date       => "%{[entry][date]}"
            picture_url   => "%{[entry][pictures][0][picture][0][picture_url]}"
            price         => "%{[entry][price][0][content]}"
        }
    }
    # Eliminar el campo "message", que contiene el mensaje original en formato XML.
    # Eliminar también el campo "entry" resultante de la conversión por defecto
    mutate {
        remove_field => ["message"]
        remove_field => ["entry"]
    }

Con esta configuración, si volvemos a ejecutar logstash obtenemos como resultado el documento JSON simplificado:

$ java -jar logstash-1.3.3-flatjar.jar agent -f importacion_xml.conf -p . < datos.xml
Using milestone 1 filter plugin 'xml'.
...
{
  "@version" => "1",
  "@timestamp" => "2015-12-19T10:53:49.262Z",
  "host" => "squeeze",
  "id" => "00634923058",
  "date" => "2015-12-15 12:00:04",
  "picture_url" => "http://cdn.mydomain.it/00634923058G.jpg",
  "price" => "450"
}
$

Preparación de la colección Solr

A continuación, vamos a preparar una colección solr “datos_xml”, que recibirá los documentos procesados por logstash. Para ello, basta con copiar la colección de ejemplo “logstash_logs”, y editar el fichero “core.properties” para especificar el nombre de la nueva colección:

$ cd SiLK/SiLK-1.5/solr-4.10.3/SiLK/solr
$ rsync -av logstash_logs/ datos_xml/
$ cd datos_xml
$ vi core.properties
    ... Sustituimos la línea "name=logstash_logs" por "name=datos_xml"
$

Importación

En primer lugar, arrancamos la instancia de solr:

$ cd SiLK/SiLK-1.5/solr-4.10.3/SiLK
$ java -Xmx5120m -jar start.jar

A continuación, desde otra ventana de terminal, procedemos a realizar la importación con el comando:

$ cd SiLK/SiLK-1.5/solrWriterForLogStash/logstash_deploy
$ zcat datos.xml.gz | php split_docs.php | grep "^<doc>" | \
        java -jar logstash-1.3.3-flatjar.jar agent -f importacion_xml.conf -l logstash-log.log -p .

Referencias:

Logstash parsing xml document containing multiple log entries

 Publicado por en 8:58 am

 Deja un comentario

(requerido)

(requerido)