Nginx Log Analytics mat Amazon Athena a Cube.js

Typesch gi kommerziell Produkter oder fäerdeg Open-Source Alternativen, wéi Prometheus + Grafana, benotzt fir d'Operatioun vun Nginx ze iwwerwaachen an ze analyséieren. Dëst ass eng gutt Optioun fir Iwwerwaachung oder Echtzäitanalyse, awer net ganz praktesch fir historesch Analyse. Op all populär Ressource wiisst de Volume vun Daten aus nginx Logbicher séier, a fir eng grouss Quantitéit un Daten ze analyséieren, ass et logesch eppes méi spezialiséiert ze benotzen.

An dësem Artikel wäert ech Iech soen wéi Dir kënnt benotzen Athena fir Logbicher ze analyséieren, Nginx als Beispill ze huelen, an ech wäert weisen wéi een en analyteschen Dashboard aus dësen Donnéeën zesummesetzt mat dem Open-Source cube.js Kader. Hei ass déi komplett Léisungsarchitektur:

Nginx Log Analytics mat Amazon Athena a Cube.js

TL:DR;
Link zum fäerdegen Dashboard.

Fir Informatiounen ze sammelen déi mir benotzen fléissend, fir d'Veraarbechtung - AWS Kinesis Data Firehose и AWS Klebstoff, fir Stockage - AWS S3. Mat dësem Bündel kënnt Dir net nëmmen nginx Logbicher späicheren, awer och aner Eventer, souwéi Logbicher vun anere Servicer. Dir kënnt e puer Deeler mat ähnlechen fir Äre Stack ersetzen, zum Beispill, Dir kënnt Logbicher op Kinesis direkt vun nginx schreiwen, fluentd ëmgoen oder Logstash fir dëst benotzen.

Sammelen Nginx Logbicher

Par défaut kucken Nginx Logbicher sou eppes aus:

4/9/2019 12:58:17 PM1.1.1.1 - - [09/Apr/2019:09:58:17 +0000] "GET /sign-up HTTP/2.0" 200 9168 "https://example.com/sign-in" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"
4/9/2019 12:58:17 PM1.1.1.1 - - [09/Apr/2019:09:58:17 +0000] "GET /sign-in HTTP/2.0" 200 9168 "https://example.com/sign-up" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36" "-"

Si kënne parséiert ginn, awer et ass vill méi einfach d'Nginx Konfiguratioun ze korrigéieren sou datt et Logbicher am JSON produzéiert:

log_format json_combined escape=json '{ "created_at": "$msec", '
            '"remote_addr": "$remote_addr", '
            '"remote_user": "$remote_user", '
            '"request": "$request", '
            '"status": $status, '
            '"bytes_sent": $bytes_sent, '
            '"request_length": $request_length, '
            '"request_time": $request_time, '
            '"http_referrer": "$http_referer", '
            '"http_x_forwarded_for": "$http_x_forwarded_for", '
            '"http_user_agent": "$http_user_agent" }';

access_log  /var/log/nginx/access.log  json_combined;

S3 fir Stockage

Fir Logbicher ze späicheren, benotze mir S3. Dëst erlaabt Iech Logbicher op enger Plaz ze späicheren an z'analyséieren, well Athena direkt mat Daten am S3 kann schaffen. Méi spéit am Artikel wäert ech Iech soen wéi Dir d'Protokoller korrekt addéiere a veraarbecht, awer als éischt brauche mir e propperen Eemer am S3, an deem näischt anescht gespäichert gëtt. Et ass derwäert am Viraus ze berücksichtegen a wéi enger Regioun Dir Ären Eemer wäert kreéieren, well Athena ass net an alle Regiounen verfügbar.

Schafe vun engem Circuit an der Athena Konsol

Loosst eis en Dësch an Athena fir Logbicher erstellen. Et ass néideg fir béid ze schreiwen an ze liesen wann Dir plangt Kinesis Firehose ze benotzen. Öffnen d'Athena Konsole a kreéiert en Dësch:

SQL Dësch Kreatioun

CREATE EXTERNAL TABLE `kinesis_logs_nginx`(
  `created_at` double, 
  `remote_addr` string, 
  `remote_user` string, 
  `request` string, 
  `status` int, 
  `bytes_sent` int, 
  `request_length` int, 
  `request_time` double, 
  `http_referrer` string, 
  `http_x_forwarded_for` string, 
  `http_user_agent` string)
ROW FORMAT SERDE 
  'org.apache.hadoop.hive.ql.io.orc.OrcSerde' 
STORED AS INPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.orc.OrcInputFormat' 
OUTPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.orc.OrcOutputFormat'
LOCATION
  's3://<YOUR-S3-BUCKET>'
TBLPROPERTIES ('has_encrypted_data'='false');

Erstellt Kinesis Firehose Stream

Kinesis Firehose schreift d'Daten, déi vun Nginx op S3 an dem gewielte Format kritt ginn, a verdeelt se an Verzeichnisser am Format JJJJ/MM/DD/HH. Dëst wäert praktesch kommen wann Dir Daten liest. Dir kënnt natierlech direkt op S3 vu fluentd schreiwen, awer an dësem Fall musst Dir JSON schreiwen, an dëst ass net effizient wéinst der grousser Gréisst vun de Dateien. Zousätzlech, wann Dir PrestoDB oder Athena benotzt, ass JSON dat luesst Dateformat. Also öffnen d'Kinesis Firehose Konsole, klickt op "Liwwerstroum erstellen", wielt "direkt PUT" am Feld "Liwwerung":

Nginx Log Analytics mat Amazon Athena a Cube.js

An der nächster Tab, wielt "Record Format Konversioun" - "Enabled" a wielt "Apache ORC" als Opnamformat. Laut e puer Fuerschungen Owen O'Malley, Dëst ass den optimale Format fir PrestoDB an Athena. Mir benotzen den Dësch, dee mir uewen erstallt hunn, als Schema. Notéiert w.e.g. datt Dir all S3 Standuert an der Kinesis spezifizéiere kënnt; nëmmen de Schema gëtt aus der Tabell benotzt. Awer wann Dir eng aner S3 Location spezifizéiert, da kënnt Dir dës Opzeechnungen aus dëser Tabell net liesen.

Nginx Log Analytics mat Amazon Athena a Cube.js

Mir wielen S3 fir Späicheren an den Eemer dee mir virdru erstallt hunn. Aws Glue Crawler, iwwer deen ech e bësse méi spéit schwätzen, kann net mat Präfixe an engem S3 Eemer schaffen, also ass et wichteg et eidel ze loossen.

Nginx Log Analytics mat Amazon Athena a Cube.js

Déi reschtlech Optiounen kënnen ofhängeg vun Ärer Belaaschtung geännert ginn; Ech benotzen normalerweis déi Standard. Notéiert datt S3 Kompressioun net verfügbar ass, awer ORC benotzt native Kompressioun als Standard.

fléissend

Elo datt mir d'Späicheren an d'Empfang vun Logbicher konfiguréiert hunn, musse mir d'Sendung konfiguréieren. Mir wäerten benotzen fléissend, well ech Ruby gär hunn, awer Dir kënnt Logstash benotzen oder Logbicher direkt op Kinesis schécken. De Fluentd Server kann op verschidde Weeër gestart ginn, ech soen Iech iwwer Docker well et einfach a praktesch ass.

Als éischt brauche mir d'Konfiguratiounsdatei fluent.conf. Erstellt et a füügt Quell dobäi:

Typ vir
Hafen 24224
binden 0.0.0.0

Elo kënnt Dir de Fluentd Server starten. Wann Dir eng méi fortgeschratt Konfiguratioun braucht, gitt op DockerHub Et gëtt en detailléierte Guide, dorënner wéi Dir Äert Bild zesummestellt.

$ docker run 
  -d 
  -p 24224:24224 
  -p 24224:24224/udp 
  -v /data:/fluentd/log 
  -v <PATH-TO-FLUENT-CONF>:/fluentd/etc fluentd 
  -c /fluentd/etc/fluent.conf
  fluent/fluentd:stable

Dës Konfiguratioun benotzt de Wee /fluentd/log Logbicher ze cache ier Dir schéckt. Dir kënnt ouni dëst maachen, awer dann, wann Dir nei start, kënnt Dir alles verléieren, dat mat der Réckbriechungsaarbecht gelagert ass. Dir kënnt och all Hafen benotzen; 24224 ass de Standard Fluentd Hafen.

Elo datt mir Fluentd lafen, kënne mir Nginx Logbicher dohinner schécken. Mir lafen normalerweis Nginx an engem Docker Container, an deem Fall huet Docker en gebiertege Logging Chauffer fir Fluentd:

$ docker run 
--log-driver=fluentd 
--log-opt fluentd-address=<FLUENTD-SERVER-ADDRESS>
--log-opt tag="{{.Name}}" 
-v /some/content:/usr/share/nginx/html:ro 
-d 
nginx

Wann Dir Nginx anescht leeft, kënnt Dir Logdateien benotzen, Fluentd huet Datei Schwäif Plugin.

Loosst eis de Log Parsing uewe konfiguréiert an d'Fluent Konfiguratioun addéieren:

<filter YOUR-NGINX-TAG.*>
  @type parser
  key_name log
  emit_invalid_record_to_error false
  <parse>
    @type json
  </parse>
</filter>

A schéckt Logbicher op Kinesis benotzt kinesis firehose plugin:

<match YOUR-NGINX-TAG.*>
    @type kinesis_firehose
    region region
    delivery_stream_name <YOUR-KINESIS-STREAM-NAME>
    aws_key_id <YOUR-AWS-KEY-ID>
    aws_sec_key <YOUR_AWS-SEC_KEY>
</match>

Athena

Wann Dir alles richteg konfiguréiert hutt, dann no enger Zäit (par défaut, Kinesis records kritt Daten eemol all 10 Minutten) sollt Dir Logdateien an S3 gesinn. Am Menü "Iwwerwaachung" vu Kinesis Firehose kënnt Dir gesinn wéi vill Daten am S3 opgeholl ginn, souwéi Feeler. Vergiesst net Schreiwen Zougang zum S3 Eemer fir d'Kinesis Roll ze ginn. Wann Kinesis eppes net parse konnt, füügt et d'Feeler an deeselwechten Eemer.

Elo kënnt Dir d'Donnéeën an Athena kucken. Loosst eis déi lescht Ufroe fannen, fir déi mir Feeler zréckginn:

SELECT * FROM "db_name"."table_name" WHERE status > 499 ORDER BY created_at DESC limit 10;

Scannen all records fir all Ufro

Elo sinn eis Logbicher veraarbecht a gespäichert an S3 am ORC, kompriméiert a prett fir Analyse. Kinesis Firehose huet se souguer an Verzeechnes fir all Stonn organiséiert. Wéi och ëmmer, soulaang den Dësch net opgedeelt ass, lued Athena all Zäit Daten op all Ufro, mat rare Ausnahmen. Dëst ass e grousse Problem aus zwee Grënn:

  • De Volume vun den Donnéeën wiisst konstant, verlangsamt Ufroen;
  • Athena gëtt berechent baséiert op dem Volume vun den gescannten Donnéeën, mat engem Minimum vun 10 MB pro Ufro.

Fir dëst ze fixéieren, benotze mir AWS Glue Crawler, deen d'Donnéeën am S3 krabbelt an d'Partitionsinformatioun an de Glue Metastore schreift. Dëst erlaabt eis Partitionen als Filter ze benotzen beim Ufro vun Athena, an et scannt nëmmen d'Verzeichnungen, déi an der Ufro spezifizéiert sinn.

Amazon Glue Crawler opsetzen

Amazon Glue Crawler scannt all d'Donnéeën am S3 Eemer a kreéiert Dëscher mat Partitionen. Erstellt e Glue Crawler vun der AWS Glue Konsole a füügt en Eemer un, wou Dir d'Donnéeën späichert. Dir kënnt e Crawler fir e puer Eemer benotzen, an deem Fall gëtt et Tabellen an der spezifizéierter Datebank mat Nimm, déi mat den Nimm vun den Eemer passen. Wann Dir plangt dës Donnéeën regelméisseg ze benotzen, gitt sécher de Crawler Startplang ze konfiguréieren fir Äre Besoinen ze passen. Mir benotzen ee Crawler fir all Dëscher, déi all Stonn leeft.

Opgedeelt Dëscher

Nom éischte Start vum Crawler sollten Dëscher fir all gescannt Eemer an der Datebank erscheinen, déi an den Astellunge spezifizéiert ass. Öffnen d'Athena Konsole a fannt den Dësch mat Nginx Logbicher. Loosst eis probéieren eppes ze liesen:

SELECT * FROM "default"."part_demo_kinesis_bucket"
WHERE(
  partition_0 = '2019' AND
  partition_1 = '04' AND
  partition_2 = '08' AND
  partition_3 = '06'
  );

Dës Ufro wäert all records auswielen, déi tëscht 6 a 7 den 8. Abrëll 2019 kritt goufen. Awer wéi vill méi effizient ass dëst wéi just aus engem net-opgedeelt Dësch ze liesen? Loosst eis erausfannen a wielt déiselwecht Opzeechnungen, filtert se no Zäitstempel:

Nginx Log Analytics mat Amazon Athena a Cube.js

3.59 Sekonnen an 244.34 Megabytes vun Daten op engem Dataset mat nëmmen enger Woch Logbicher. Loosst eis e Filter duerch Partition probéieren:

Nginx Log Analytics mat Amazon Athena a Cube.js

E bësse méi séier, awer am wichtegsten - nëmmen 1.23 Megabytes vun Daten! Et wier vill méi bëlleg wann net fir de Minimum 10 Megabytes pro Ufro am Präiss. Awer et ass nach ëmmer vill besser, an op groussen Datesätz wäert den Ënnerscheed vill méi beandrockend sinn.

Bauen en Dashboard mat Cube.js

Fir den Dashboard ze sammelen, benotze mir den analytesche Kader Cube.js. Et huet zimmlech vill Funktiounen, awer mir sinn an zwee interesséiert: d'Fäegkeet fir automatesch Partitionsfilteren an d'Pre-Aggregatioun vun Daten ze benotzen. Et benotzt Dateschema daten Schema, geschriwwen an Javascript fir SQL ze generéieren an eng Datebank Ufro auszeféieren. Mir mussen nëmmen uginn wéi Dir de Partitionsfilter am Dateschema benotzt.

Loosst eis eng nei Cube.js Applikatioun erstellen. Well mir schonn den AWS Stack benotzen, ass et logesch Lambda fir den Deployment ze benotzen. Dir kënnt d'Express Template fir d'Generatioun benotzen wann Dir plangt de Cube.js Backend an Heroku oder Docker ze hosten. D'Dokumentatioun beschreift anerer Hosting Methoden.

$ npm install -g cubejs-cli
$ cubejs create nginx-log-analytics -t serverless -d athena

Ëmweltvariablen gi benotzt fir den Zougang zu der Datebank an cube.js ze konfiguréieren. De Generator wäert eng .env Datei erstellen, an där Dir Är Schlësselen fir Athena.

Elo brauche mir daten Schema, an deem mir genau uginn wéi eis Logbicher gespäichert sinn. Do kënnt Dir och spezifizéieren wéi Dir Metriken fir Dashboards berechent.

Am Verzeechnes schema, eng Datei erstellen Logs.js. Hei ass e Beispill Datemodell fir nginx:

Modell Code

const partitionFilter = (from, to) => `
    date(from_iso8601_timestamp(${from})) <= date_parse(partition_0 || partition_1 || partition_2, '%Y%m%d') AND
    date(from_iso8601_timestamp(${to})) >= date_parse(partition_0 || partition_1 || partition_2, '%Y%m%d')
    `

cube(`Logs`, {
  sql: `
  select * from part_demo_kinesis_bucket
  WHERE ${FILTER_PARAMS.Logs.createdAt.filter(partitionFilter)}
  `,

  measures: {
    count: {
      type: `count`,
    },

    errorCount: {
      type: `count`,
      filters: [
        { sql: `${CUBE.isError} = 'Yes'` }
      ]
    },

    errorRate: {
      type: `number`,
      sql: `100.0 * ${errorCount} / ${count}`,
      format: `percent`
    }
  },

  dimensions: {
    status: {
      sql: `status`,
      type: `number`
    },

    isError: {
      type: `string`,
      case: {
        when: [{
          sql: `${CUBE}.status >= 400`, label: `Yes`
        }],
        else: { label: `No` }
      }
    },

    createdAt: {
      sql: `from_unixtime(created_at)`,
      type: `time`
    }
  }
});

Hei benotze mir d'Variabel FILTER_PARAMSfir eng SQL Ufro mat engem Partitionsfilter ze generéieren.

Mir setzen och d'Metriken an d'Parameteren, déi mir um Dashboard wëllen weisen an d'Pre-Aggregatioune spezifizéieren. Cube.js wäert zousätzlech Dëscher mat pre-aggregéierten Donnéeën erstellen an d'Donnéeën automatesch aktualiséieren wéi se ukommen. Dëst beschleunegt net nëmmen Ufroen, awer reduzéiert och d'Käschte fir Athena ze benotzen.

Loosst eis dës Informatioun an d'Dateschemadatei addéieren:

preAggregations: {
  main: {
    type: `rollup`,
    measureReferences: [count, errorCount],
    dimensionReferences: [isError, status],
    timeDimensionReference: createdAt,
    granularity: `day`,
    partitionGranularity: `month`,
    refreshKey: {
      sql: FILTER_PARAMS.Logs.createdAt.filter((from, to) => 
        `select
           CASE WHEN from_iso8601_timestamp(${to}) + interval '3' day > now()
           THEN date_trunc('hour', now()) END`
      )
    }
  }
}

Mir spezifizéieren an dësem Modell datt et néideg ass d'Donnéeën ze pre-aggregeren fir all benotzt Metriken, an d'Partitionéierung vum Mount ze benotzen. Pre-Aggregatioun Partitionéieren kann d'Datensammlung an d'Aktualiséierung wesentlech beschleunegen.

Elo kënne mir den Dashboard zesummestellen!

Cube.js Backend bitt Rescht API an eng Rei vu Clientbibliothéike fir populär Front-End Kaderen. Mir benotzen d'React Versioun vum Client fir den Dashboard ze bauen. Cube.js liwwert nëmmen Daten, also brauche mir eng Visualiséierungsbibliothéik - ech hunn et gär recharts, mee du kanns all benotzen.

De Cube.js Server akzeptéiert d'Ufro an JSON Format, déi déi néideg Metriken spezifizéiert. Zum Beispill, fir ze zielen wéivill Feeler Nginx am Dag ginn huet, musst Dir déi folgend Ufro schécken:

{
  "measures": ["Logs.errorCount"],
  "timeDimensions": [
    {
      "dimension": "Logs.createdAt",
      "dateRange": ["2019-01-01", "2019-01-07"],
      "granularity": "day"
    }
  ]
}

Loosst eis de Cube.js Client an d'React Komponentbibliothéik iwwer NPM installéieren:

$ npm i --save @cubejs-client/core @cubejs-client/react

Mir importéieren Komponenten cubejs и QueryRendererfir d'Donnéeën erofzelueden an d'Dashboard ze sammelen:

Dashboard Code

import React from 'react';
import { LineChart, Line, XAxis, YAxis } from 'recharts';
import cubejs from '@cubejs-client/core';
import { QueryRenderer } from '@cubejs-client/react';

const cubejsApi = cubejs(
  'YOUR-CUBEJS-API-TOKEN',
  { apiUrl: 'http://localhost:4000/cubejs-api/v1' },
);

export default () => {
  return (
    <QueryRenderer
      query={{
        measures: ['Logs.errorCount'],
        timeDimensions: [{
            dimension: 'Logs.createdAt',
            dateRange: ['2019-01-01', '2019-01-07'],
            granularity: 'day'
        }]
      }}
      cubejsApi={cubejsApi}
      render={({ resultSet }) => {
        if (!resultSet) {
          return 'Loading...';
        }

        return (
          <LineChart data={resultSet.rawData()}>
            <XAxis dataKey="Logs.createdAt"/>
            <YAxis/>
            <Line type="monotone" dataKey="Logs.errorCount" stroke="#8884d8"/>
          </LineChart>
        );
      }}
    />
  )
}

Dashboard Quelle sinn op Code Sandkëscht.

Source: will.com

Setzt e Commentaire