JSON in bequem

Wenn man mit diversen Tools arbeitet, die wir in diesem Blog immer wieder bearbeitet, stößt man unweigerlich irgendwann auf JSON formatierte Texte. Während es sicher für den einen oder anderen IT-God kein Problem ist, das JSON im Hirn zu parsen und JSON-formatierter Text höchstens eine angenehme Erleichterung zu Bytecode ist, möchte ich hier kurz zweieinhalb Wege für Sterbliche vorstellen, um im
Wirrwarr von Klammern nicht die Übersicht zu verlieren.
Dabei bringen manche Dienste bereits eine Möglichkeit mit, den Output einfacher lesbar zu machen, andere verlassen sich dabei ganz auf externe Tools. Warum er nicht immer “einfach” gehalten wird, lässt sich ganz einfach damit erklären, das JSON eingentlich eh nur entworfen wurde, um von Maschinen verarbeitet zu werden und Menschen in den Wahnsinn zu treiben. Da sorgen Zeilenumbrüche und Einrückungen nur dafür, dass unnötig viele Daten übertragen werden.
Die REST API von Elasticsearch bietet je nach Endpunkt mal schön formatiertes, mal unformatierters JSON an.

# curl localhost:9200
{
  "name" : "y2G7v2X",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "1KYRb5mUQ8CaTSJDM-6djQ",
  "version" : {
    "number" : "6.3.2",
    "build_flavor" : "default",
    "build_type" : "rpm",
    "build_hash" : "053779d",
    "build_date" : "2018-07-20T05:20:23.451332Z",
    "build_snapshot" : false,
    "lucene_version" : "7.3.1",
    "minimum_wire_compatibility_version" : "5.6.0",
    "minimum_index_compatibility_version" : "5.0.0"
  },
  "tagline" : "You Know, for Search"
}

Dagegen sieht jeder Unterpunkt entsprechend unübersichtlich aus.

# curl localhost:9200/_cluster/health
{"cluster_name":"elasticsearch","status":"green","timed_out":false,"number_of_nodes":2,"number_of_data_nodes":2,"active_primary_shards":341,"active_shards":682,"relocating_shards":0,"initializing_shards":0,"unassigned_shards":0,"delayed_unassigned_shards":0,"number_of_pending_tasks":0,"number_of_in_flight_fetch":0,"task_max_waiting_in_queue_millis":0,"active_shards_percent_as_number":100.0}

Für Elasticsearch (und mittlerweile auch für Icinga 2!) gibt’s hier aber Abhilfe durch den Schalter ?pretty.

# curl localhost:9200/_cluster/health?pretty
{
  "cluster_name" : "elasticsearch",
  "status" : "green",
  "timed_out" : false,
  "number_of_nodes" : 2,
  "number_of_data_nodes" : 2,
  "active_primary_shards" : 341,
  "active_shards" : 682,
  "relocating_shards" : 0,
  "initializing_shards" : 0,
  "unassigned_shards" : 0,
  "delayed_unassigned_shards" : 0,
  "number_of_pending_tasks" : 0,
  "number_of_in_flight_fetch" : 0,
  "task_max_waiting_in_queue_millis" : 0,
  "active_shards_percent_as_number" : 100.0
}

Da man diverse JSON Monster aber auch mal als Datei vorliegen hat oder nicht alle Dienste solche Komfortfunktionen bieten, braucht man immer wieder externe Hilfe. Dafür gibt’s die Allroundlösung, die überall funktioniert, wo Python installiert ist, also quasi bei jedem ernst zu nehmenden Betriebssystem.

# curl -s localhost:9200/_cluster/health | python -m json.tool
{
    "active_primary_shards": 341,
    "active_shards": 682,
    "active_shards_percent_as_number": 100.0,
    "cluster_name": "elasticsearch",
    "delayed_unassigned_shards": 0,
    "initializing_shards": 0,
    "number_of_data_nodes": 2,
    "number_of_in_flight_fetch": 0,
    "number_of_nodes": 2,
    "number_of_pending_tasks": 0,
    "relocating_shards": 0,
    "status": "green",
    "task_max_waiting_in_queue_millis": 0,
    "timed_out": false,
    "unassigned_shards": 0
}

Das war’s dann aber auch schon mit dem, was man einfach auf diese Weise machen kann. Oft reicht das ja auch aus. Wer aber gern etwas mehr an Möglichkeiten haben will, sollte sich unbedingt mal jq installieren.

# curl -s localhost:9200/_cluster/health | jq
{
  "cluster_name": "elasticsearch",
  "status": "green",
  "timed_out": false,
  "number_of_nodes": 2,
  "number_of_data_nodes": 2,
  "active_primary_shards": 341,
  "active_shards": 682,
  "relocating_shards": 0,
  "initializing_shards": 0,
  "unassigned_shards": 0,
  "delayed_unassigned_shards": 0,
  "number_of_pending_tasks": 0,
  "number_of_in_flight_fetch": 0,
  "task_max_waiting_in_queue_millis": 0,
  "active_shards_percent_as_number": 100
}

Soweit so fad. Ausser, dass ich auf meiner Shell noch schöne farbliche Hervorhebungen sehe und Ihr im Blog hier nicht. 😛
Interessant wird’s dann aber, wenn man mit jq anfängt, den Output auch gleich zu filtern. Dazu ein Auszug eines API Outputs von Icinga 2.

{
  "results": [
    {
      "attrs": {
        "__name": "canis",
        "acknowledgement": 0.0,
        "acknowledgement_expiry": 0.0,
[...]
    },
    "joins": {},
    "meta": {},
    "name": "canis",
    "type": "Host"
  },
[...]

Wenn man den Output dann durch einen jq Aufruf inkl. Filtern schickt, kann man auf die eigentlich interessanten Daten filtern. Ich hab’ mir freundlicherweise genehmigt, die folgenden Beispiele aus dem Icinga 2 Buch zu entnehmen.

$ curl ... |jq '{name: .results[].name}'
{
  "name": "canis"
}
{
  "name": "fornax"
}
{
  "name": "virgo"
}
[...]

Diese Filter kann man dann beliebig erweitern.

$ curl ... |jq '{name: .results[].name, address: .results[].attrs.address}'
{
  "name": "sculptor"
  "address": "172.16.2.11"
}
{
  "name": "fornax"
  "address": "172.16.1.11"
}
...

Eine umfassende Anleitung für jq gibt’s auch. Je nach Bedarf kann es sich aber auszahlen, sich erstmal diverse Tutorials anzusehen, da hier oft der Zugang etwas leichter gemacht wird.

Thomas Widhalm
Thomas Widhalm
Lead Support Engineer

Thomas war Systemadministrator an einer österreichischen Universität und da besonders für Linux und Unix zuständig. Seit 2013 möchte er aber lieber die große weite Welt sehen und hat sich deshalb dem Netways Consulting Team angeschlossen. Er möchte ausserdem möglichst weit verbreiten, wie und wie einfach man persönliche Kommunikation sicher verschlüsseln kann, damit nicht dauernd über fehlenden Datenschutz gejammert, sondern endlich was dagegen unternommen wird. Mittlerweile wird er zum logstash - Guy bei Netways und hält...

Get up and meet those pretty tags, tags, tags

Heute gibt es mal einen Tip direkt aus der Praxis. Wer sich schon länger mit logstash beschäftigt, wird bemerkt haben, dass sich das Format der Konfigurationsdateien mit der Version 1.3 etwas verändert hat, wobei die alte Form nach wie vor gültig ist. Geändert hat sich vor allem der Weg, wie tags und types zum Festlegen der Filter und Outputs verwendet werden, die ein Event durchläuft.
Dass es durchaus seine Tücken haben kann, dass das alte Format weiterhin seine Gültigkeit hat, habe ich kürzlich in einer Schulung am eigenen Leib erfahren müssen. Das hat allerdings auch sein Gutes. Erstens wird mir das ganz sicher nicht mehr passieren und zweitens habe ich so ein Thema, über das ich bloggen kann.
Auch wenn neuere User sicherlich inzwischen das neue Format verwenden, so stösst man doch immer wieder online auf Beispiele im alten Format und wenn man die per copy&paste übernimmt, passieren ganz leicht Dinge, die man so nicht vorhergesagt hätte.
logstash_01
Zum Vergleich erstmal, wie sich tags im alten Format verhalten. In input Blöcken bedeutet die Option tags, dass Tags zum Event hinzugefügt werden. In filter und output Blöcken, dass der Filter oder der Output nur auf Events angewendet wird, die dieses Tag bereits haben. Seit logstash 1.3 verwendet man stattdessen if Verzweigungen. Um tags in Filtern und Outputs hinzuzufügen, verwendet man übrigens add_tag
Mit Types verhält es sich ganz ähnlich, auch wenn man den nur im ersten logstash Input, den das Event durchläuft, setzen und nicht mehr verändern kann.
Also wird der erste noop Filter im folgenden Beispiel nur auf Events aus dem ersten Input angewendet, der zweite noop nur auf Events aus dem zweiten Input.

input {
  stdin {
    tags => stdin
    type => debuglog
  }
  tcp {
    port => 5514
    tags => tcp
    type => syslog
  }
}
filter {
  noop {
    tags => stdin
    add_tag => noop1
  }
  noop {
    type => syslog
    add_tag => noop2
}

Also merke: tags ist nicht gleich tags
In der neueren und aktuellen Form würde man das Beispiel von oben übrigens folgendermassen schreiben.

input {
  stdin {
    tags => stdin
    type => debuglog
  }
  tcp {
    port => 5514
    tags => tcp
    type => syslog
  }
}
filter {
  if ¨stdin¨ in [tags] {
    noop {
      add_tag => noop1
    }
  }
  if [type] == ¨syslog¨ {
    noop {
      add_tag => noop2
    }
  }
}

Das Beispiel soll möglichst deutlich zeigen, was das Verwirrende an dem Unterschied der beiden Schreibweisen ist und ist deshalb selbst bewusst nicht verwirrend gehalten.
Was dadurch regelmässig passiert ist, dass jemand die Option tags in einem Filter oder Output sieht und davon ausgeht, dass dadurch ein weiterer Tag gesetzt wird, nicht, dass dies eine Einschränkung auf gewisse Events darstellt. Will ich fälschlicherweise ein bisher nicht vorhandenes Tag mit tags in einem Filter setzen, wird dieser Filter natürlich nie angewandt, da ja nie ein Event mit diesem Tag kommen wird, da ich ihn ja erst setzen möchte. Für Neulinge ist das ein Stolperstein, bis diese Möglichkeit aus dem Format entfernt wird, aber auch Leuten, die intensiv mit logstash arbeiten, kann sowas als Flüchtigkeitsfehler passieren, wie ich mir eben vor kurzem selbst bewiesen habe.

Thomas Widhalm
Thomas Widhalm
Lead Support Engineer

Thomas war Systemadministrator an einer österreichischen Universität und da besonders für Linux und Unix zuständig. Seit 2013 möchte er aber lieber die große weite Welt sehen und hat sich deshalb dem Netways Consulting Team angeschlossen. Er möchte ausserdem möglichst weit verbreiten, wie und wie einfach man persönliche Kommunikation sicher verschlüsseln kann, damit nicht dauernd über fehlenden Datenschutz gejammert, sondern endlich was dagegen unternommen wird. Mittlerweile wird er zum logstash - Guy bei Netways und hält...

Ruby zaubert mit Excel und Spreadsheets

In der heutigen Arbeitswelt werden viele Daten in Spreadsheets oder Exceltabellen verwaltet.
Ein paar Tage ist es her, da galt es für mich anhand einer Exceltabelle Infos zu einem Projekt zu sammeln und mit zugehörigen Dateien in ein Verzeichnis zu verpacken.
Dabei war es Fakt die Datensätze der Tabelle auszulesen und mit den Datensätzen in der Datenbank zu vergleichen.
Bei Google stößt man dabei schnell auf “CSV Spreadsheets lesen” nur dann wird das Konvertieren und Selektieren zur schmerzlichen Angelegenheit.
Um größere Schäden zu vermeiden, gibt es schlaue Magier die immer ein Gem in dem Zylinder haben.
Dazu stelle ich euch das RubyGem “Roo” vor, dass das Verarbeiten von Excelsheets zu einem Kinderspiel macht.
Ich werde mich bei dem Beispiel nur auf den kleinsten Anwendungsfall beziehen.
Und dazu installieren wir das Gem zuerst:

$ sudo ruby gem install roo

Unser Beispiel ist eine Spreadsheet im .xlsx Format.
screenshot1

#!/usr/bin/env ruby
require 'rubygems'
require 'roo'
foo = Roo::Excelx.new("projects.xlsx")
foo.default_sheet = foo.sheets.first
2.upto(8) do |line|
  project_title = foo.cell(line,'A')
  type = foo.cell(line, 'B')
  vendor = foo.cell(line, 'C')
  target = foo.cell(line, 'D')
  puts "#{project_title}\t#{type}\t#{vendor}\t#{target}"
end
foo.to_csv("foo.csv")

Mit diesem Zehnzeiler wird für die Zeile 2 bis 4 der jeweilige Inhalt einer Spalte
in die dafür definierte Variable geschrieben.
Mit “puts” wird der Inhalt der Variable an die Ausgabe gegeben.
Bildschirmfoto 2014-05-06 um 12.20.26
Falls die Excelsheets zu lang sind um ein Ende definieren zu können kann das upto() mit weiteren
Optionen ausgeführt werden.

first_column,
last_column,
first_row and
last_row

Auf unseren Fall angepasst, schaut das so aus:

2.upto(foo.last_row) do |line|

Mit diesem Gem macht auch das Verarbeiten von großen Exceltabellen wieder einen Sinn.
Weitere Infos findet Ihr auf der Homepage von Roo
Ich kann dazu nur noch sagen “Just awesome!” oder auch “It’s a kind of magic!”
Mehr coole Tricks und Magie findet Ihr auf unserem Blog

Thilo Wening
Thilo Wening
Senior Consultant

Thilo hat bei NETWAYS mit der Ausbildung zum Fachinformatiker, Schwerpunkt Systemadministration begonnen und unterstützt nun nach erfolgreich bestandener Prüfung tatkräftig die Kollegen im Consulting. In seiner Freizeit ist er athletisch in der Senkrechten unterwegs und stählt seine Muskeln beim Bouldern. Als richtiger Profi macht er das natürlich am liebsten in der Natur und geht nur noch in Ausnahmefällen in die Kletterhalle.