Normalt bruges kommercielle produkter eller færdige open-source alternativer som Prometheus + Grafana til overvågning og analyse af Nginx. Dette er en god mulighed for overvågning eller real-time analyse, men ikke særligt praktisk til historisk analyse. På enhver populær ressource vokser datamængden fra Nginx-logfiler hurtigt, og for at analysere store datamængder giver det mening at bruge noget mere specialiseret.
I denne artikel vil jeg fortælle, hvordan man kan bruge til at analysere logfiler, med Nginx som eksempel, og vise, hvordan man kan samle et analytisk dashboard af disse data ved at bruge open-source rammeværket cube.js. Her er den komplette arkitektur for løsningen:

TL:DR;
.
For at samle information bruger vi , til behandling — og , til opbevaring — . Med dette setup kan man ikke kun opbevare Nginx-logfiler, men også andre hændelser samt logfiler fra andre tjenester. Du kan erstatte nogle dele med de der er relevante for din tech-stack, for eksempel kan man skrive logfiler direkte til Kinesis fra Nginx uden at gå gennem Fluentd, eller bruge Logstash til det.
Indsamle Nginx-logfiler
Som standard ser Nginx-logfiler således ud:
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" "-"De kan parses, men det er meget lettere at ændre Nginx-konfigurationen, så den genererer logfiler i JSON:
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 til opbevaring
For at opbevare logfilerne vil vi bruge S3. Det gør det muligt at gemme og analysere logfiler på ét sted, da Athena kan arbejde med data i S3 direkte. Længere inde i artiklen vil jeg fortælle, hvordan man korrekt opbevarer og behandler logfiler, men først har vi brug for en ren bucket i S3, hvor der ikke vil være andet gemt. Det er værd at tænke over, hvilken region du vil oprette bucketen i, da Athena ikke er tilgængelig i alle regioner.
Opretter et skema i Athena-konsollen
Lad os oprette en tabel i Athena til logs. Den er nødvendig både til skrivning og læsning, hvis du planlægger at bruge Kinesis Firehose. Åbn Athena-konsollen og opret en tabel:
SQL til at oprette tabel
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://'
TBLPROPERTIES ('has_encrypted_data'='false');Opretter en Kinesis Firehose Stream
Kinesis Firehose vil gemme de data, der modtages fra Nginx, i S3 i det valgte format, opdelt i mapper i formatet ÅÅÅÅ/MM/DD/TT. Dette vil være nyttigt ved læsning af data. Man kan selvfølgelig skrive direkte til S3 fra fluentd, men i så fald skal man skrive JSON, hvilket ikke er effektivt på grund af størrelsen af filerne. Desuden er JSON det langsomste dataformat, når man bruger PrestoDB eller Athena. Så åbner vi Kinesis Firehose-konsollen, klikker på "Create delivery stream", vælger "direct PUT" i feltet "delivery":

I næste fane vælger vi "Record format conversion" - "Enabled" og vælger "Apache ORC" som format til skrivning. Ifølge nogle undersøgelser , er dette det optimale format for PrestoDB og Athena. Som skema angiver vi den tabel, vi oprettede ovenfor. Bemærk, at S3-lokationen i kinesis kan angives til hvad som helst; kun skemaet bruges fra tabellen. Men hvis du angiver en anden S3-lokation, kan du ikke læse disse posteringer fra tabellen.

Vi vælger S3 til opbevaring og den bucket, som vi oprettede tidligere. Aws Glue Crawler, som jeg vil fortælle om senere, kan ikke arbejde med præfikser i S3-bucket, så det er vigtigt at lade den være tom.

De øvrige indstillinger kan ændres afhængigt af din belastning, jeg bruger normalt de standardindstillinger. Bemærk, at komprimering af S3 ikke er tilgængelig, men ORC bruger sin egen komprimering som standard.
Fluentd
Nu hvor vi har opsat opbevaring og modtagelse af logs, skal vi opsætte afsendelse. Vi vil bruge , fordi jeg elsker Ruby, men du kan bruge Logstash eller sende logs til kinesis direkte. Fluentd-serveren kan startes på flere måder, jeg vil tale om docker, fordi det er enkelt og praktisk.
For at starte har vi brug for en konfigurationsfil fluent.conf. Opret den og tilføj source:
forward
port 24224
bind 0.0.0.0
Nu kan Fluentd-serveren startes. Hvis du har brug for en mere avanceret konfiguration, er der en detaljeret guide, herunder hvordan man bygger sit eget billede.
$ docker run
-d
-p 24224:24224
-p 24224:24224/udp
-v /data:/fluentd/log
-v :/fluentd/etc fluentd
-c /fluentd/etc/fluent.conf
fluent/fluentd:stableDenne konfiguration bruger stien /fluentd/log til at cache logs, før de sendes. Dette kan være undgået, men så kan al cached arbejde gå tabt ved genstart. En hvilken som helst port kan bruges, 24224 er standardporten for Fluentd.
Nu når vi har startet Fluentd, kan vi sende logs fra Nginx dertil. Vi kører normalt Nginx i en Docker-container, og i så fald har Docker en indbygget logdriver til Fluentd:
$ docker run
--log-driver=fluentd
--log-opt fluentd-address=
--log-opt tag="{{.Name}}"
-v /some/content:/usr/share/nginx/html:ro
-d
nginxHvis du kører Nginx på en anden måde, kan du bruge logfiler, i Fluentd findes der .
Lad os tilføje log parsing i Fluentd konfigurationen, som er indstillet ovenfor:
@type parser
key_name log
emit_invalid_record_to_error false
@type jsonOg send logs til Kinesis ved hjælp af :
@type kinesis_firehose
region region
delivery_stream_name
aws_key_id
aws_sec_keyAthena
Hvis du har konfigureret alt korrekt, skal du inden for kort tid (som standard registrerer Kinesis modtagne data hver 10. minut) se logfiler i S3. I menuen 'monitoring' Kinesis Firehose kan du se, hvor meget data der er registreret i S3, samt fejl. Glem ikke at give skriveadgang til S3-bucketen for Kinesis-rollen. Hvis Kinesis ikke kan parse noget, vil den placere fejl i den samme bucket.
Nu kan vi se dataene i Athena. Lad os finde de seneste forespørgsler, som vi har givet fejl:
SELECT * FROM "db_name"."table_name" WHERE status > 499 ORDER BY created_at DESC limit 10;Scanning af alle poster ved hver forespørgsel
Nu er vores logs blevet behandlet og gemt i S3 i ORC, komprimeret og klar til analyse. Kinesis Firehose har endda organiseret dem i mapper for hver time. Men indtil tabellen er partitioneret, vil Athena indlæse data for hele tiden ved hver forespørgsel, med sjældne undtagelser. Dette er et stort problem af to grunde:
- Mængden af data vokser konstant, hvilket sænker forespørgslerne;
- Prisen for Athena opkræves baseret på mængden af scannede data, med et minimum på 10 MB per forespørgsel.
For at rette dette bruger vi AWS Glue Crawler, som scanner dataene i S3 og registrerer oplysninger om partitioner i Glue Metastore. Dette vil give os mulighed for at bruge partitioner som filter ved forespørgsler i Athena, og den vil kun scanne de mapper, der er angivet i forespørgslen.
Konfigurerer Amazon Glue Crawler
Amazon Glue Crawler scanner alle dataene i S3-bucket'en og opretter tabeller med partitioner. Opret Glue Crawler fra AWS Glue-konsollen og tilføj bucket'en, hvor du opbevarer dataene. Du kan bruge en enkelt crawler til flere buckets; i så fald opretter den tabeller i den angivne database med navne, der matcher bucket-navnene. Hvis du planlægger at bruge disse data kontinuerligt, skal du huske at indstille en tidsplan for Crawleren i henhold til dine behov. Vi bruger én Crawler til alle tabeller, som køres hver time.
Partitionerede tabeller
Efter den første kørsel af crawleren skulle der i databasen, angivet i indstillingerne, dukke tabeller op for hver scannet bucket. Åbn Athena-konsollen, og find tabellen med Nginx-logs. Lad os prøve at læse noget:
SELECT * FROM "default"."part_demo_kinesis_bucket"
WHERE(
partition_0 = '2019' AND
partition_1 = '04' AND
partition_2 = '08' AND
partition_3 = '06'
);Denne forespørgsel vil vælge alle poster, der blev modtaget fra kl. 6 til 7 den 8. april 2019. Men hvor meget mere effektivt er det end blot at læse fra en ikke-partitioneret tabel? Lad os finde ud af det og vælge de samme poster filtreret efter timestamp:

3,59 sekunder og 244,34 megabyte data på et datasæt, der kun har en uges logs. Lad os prøve filtrering efter partitioner:

Lidt hurtigere, men det vigtigste — kun 1,23 megabyte data! Det ville være meget billigere, hvis ikke for de minimale 10 megabyte pr. forespørgsel i prissætningen. Men det er stadig meget bedre, og forskellen vil være langt mere imponerende på større datasæt.
Собираем дэшборд с помощью Cube.js
For at samle et dashboard bruger vi det analytiske framework Cube.js. Det har en del funktioner, men vi er interesserede i to: muligheden for automatisk at anvende filtre baseret på partitioner og præ-aggregationen af data. Det bruger et dataskema , skrevet i Javascript, for at generere SQL og udføre forespørgslen mod databasen. Vi skal blot angive, hvordan man bruger filteret for partitioner i dataskemaet.
Lad os oprette en ny Cube.js-applikation. Da vi allerede bruger et AWS-stacks, er det logisk at bruge Lambda til deployment. Du kan bruge express-skabelonen til generering, hvis du planlægger at hoste Cube.js-backend i Heroku eller Docker. Dokumentationen beskriver andre .
$ npm install -g cubejs-cli
$ cubejs create nginx-log-analytics -t serverless -d athenaFor at konfigurere adgangen til databasen i cube.js bruges miljøvariabler. Generatoren vil oprette en .env fil, hvor du kan angive dine nøgler til .
Nu har vi brug for , hvor vi angiver, hvordan vores logs opbevares. Her kan vi også angive, hvordan vi beregner målepunkter til dashboards.
I mappen schema, opret en fil Logs.js. Her er et eksempel på datamodel for nginx:
Modelkode
const partitionFilter = (from, to) => `
date(from_iso8601_timestamp(${from})) = 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`
}
}
});Her bruger vi variablen , for at generere SQL-forespørgslen med filteret for partitioner.
Vi definerer også de målepunkter og parametre, vi ønsker at vise på dashboardet, og angiver pre-aggregater. Cube.js vil oprette ekstra tabeller med pre-aggregaterede data og vil automatisk opdatere dataene, efterhånden som de kommer ind. Dette gør ikke kun forespørgsler hurtigere, men reducerer også omkostningerne ved brug af Athena.
Lad os tilføje denne information til dataschemas filen:
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`
)
}
}
}Vi angiver i denne model, at det er nødvendigt at pre-aggregere dataene for alle anvendte målepunkter og bruge partitionering pr. måned. kan betydeligt fremskynde indsamlingen og opdateringen af data.
Nu kan vi samle dashboardet!
Backend Cube.js leverer og et sæt klientbiblioteker til populære frontend-rammer. Vi vil bruge React-versionen af klienten til at opbygge dashboardet. Cube.js leverer kun data, så vi har brug for et bibliotek til visualiseringer – jeg kan godt lide , men du kan bruge hvad som helst.
Serveren Cube.js modtager anmodninger i , hvor de nødvendige målinger angives. For eksempel, for at tælle, hvor mange fejl Nginx har sendt pr. dag, skal du sende en sådan anmodning:
{
"measures": ["Logs.errorCount"],
"timeDimensions": [
{
"dimension": "Logs.createdAt",
"dateRange": ["2019-01-01", "2019-01-07"],
"granularity": "day"
}
]
}Lad os installere Cube.js klienten og React-komponenten via NPM:
$ npm i --save @cubejs-client/core @cubejs-client/reactVi importerer komponenterne cubejs og QueryRenderer, for at trække data, og opbygger dashboardet:
Koden til dashboardet
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 (
{
if (!resultSet) {
return 'Loading...';
}
return (
);
}}
/>
)
}Kildekoden til dashboardet er tilgængelig på .
Kilde: habr.com
