éåžžãåçšè£œåããPrometheus + Grafana ãªã©ã®æ¢è£œã®ãªãŒãã³ãœãŒã¹ä»£æ¿åããNginx ã®åäœã®ç£èŠãšåæã«äœ¿çšãããŸãã ããã¯ç£èŠããªã¢ã«ã¿ã€ã åæã«ã¯è¯ããªãã·ã§ã³ã§ãããå±¥æŽåæã«ã¯ããŸã䟿å©ã§ã¯ãããŸããã äžè¬çãªãªãœãŒã¹ã§ã¯ãnginx ãã°ã®ããŒã¿éãæ¥éã«å¢å ããŠããããã倧éã®ããŒã¿ãåæããã«ã¯ãããç¹åãããã®ã䜿çšããã®ãåççã§ãã
ãã®èšäºã§ã¯ãã®äœ¿ãæ¹ã玹ä»ããŸã
TLïŒDR;
åœç€Ÿã䜿çšããæ
å ±ãåéãããã
Nginx ãã°ã®åé
ããã©ã«ãã§ã¯ãNginx ãã°ã¯æ¬¡ã®ããã«ãªããŸãã
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" "-"
ãããã¯è§£æã§ããŸãããJSON ã§ãã°ãçæãããããã« Nginx æ§æãä¿®æ£ããæ¹ãã¯ããã«ç°¡åã§ãã
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
ãã°ãä¿åããã«ã¯ãS3 ã䜿çšããŸãã ããã«ãããAthena 㯠S3 å ã®ããŒã¿ãçŽæ¥æäœã§ããããããã°ã 3 ãæã«ä¿åããŠåæã§ããŸãã ãã®èšäºã®åŸåã§ã¯ããã°ãæ£ããè¿œå ããŠåŠçããæ¹æ³ã«ã€ããŠèª¬æããŸãããæåã«ãä»ã«äœãä¿åãããªããSXNUMX å ã®ã¯ãªãŒã³ãªãã±ãããå¿ èŠã§ãã Athena ã¯ãã¹ãŠã®ãªãŒãžã§ã³ã§å©çšã§ããããã§ã¯ãªããããã©ã®ãªãŒãžã§ã³ã§ãã±ãããäœæããããäºåã«æ€èšãã䟡å€ããããŸãã
Athena ã³ã³ãœãŒã«ã§ã®åè·¯ã®äœæ
Athena ã«ãã°çšã®ããŒãã«ãäœæããŸãããã Kinesis Firehose ã䜿çšããå Žåã¯ãæžã蟌ã¿ãšèªã¿åãã®äž¡æ¹ã«å¿ èŠã§ãã Athena ã³ã³ãœãŒã«ãéããŠããŒãã«ãäœæããŸãã
SQLããŒãã«ã®äœæ
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');
Kinesis Firehose ã¹ããªãŒã ã®äœæ
Kinesis Firehose ã¯ãNginx ããåä¿¡ããããŒã¿ãéžæãã圢åŒã§ S3 ã«æžã蟌ã¿ãYYYY/MM/DD/HH 圢åŒã®ãã£ã¬ã¯ããªã«åå²ããŸãã ããŒã¿ãèªã¿åããšãã«äŸ¿å©ã§ãã ãã¡ãããfluentd ãã S3 ã«çŽæ¥æžã蟌ãããšãã§ããŸããããã®å Žå㯠JSON ãæžã蟌ãå¿ èŠãããããã¡ã€ã« ãµã€ãºã倧ããããéå¹çã§ãã ããã«ãPrestoDB ãŸã㯠Athena ã䜿çšããå ŽåãJSON ã¯æãé ãããŒã¿åœ¢åŒã§ãã ããã§ãKinesis Firehose ã³ã³ãœãŒã«ãéãããé ä¿¡ã¹ããªãŒã ã®äœæããã¯ãªãã¯ãããé ä¿¡ããã£ãŒã«ãã§ãdirect PUTããéžæããŸãã
次ã®ã¿ãã§ããèšé²ãã©ãŒãããå€æã-ãæå¹ããéžæããèšé²ãã©ãŒããããšããŠãApache ORCããéžæããŸãã ããç 究ã«ãããš
ã¹ãã¬ãŒãžãšã㊠S3 ãéžæããåã«äœæãããã±ãããéžæããŸãã Aws Glue Crawler ã¯ãåŸã»ã©èª¬æããŸãããS3 ãã±ããå ã®ãã¬ãã£ãã¯ã¹ãåŠçã§ããªãããã空ã®ãŸãŸã«ããŠããããšãéèŠã§ãã
æ®ãã®ãªãã·ã§ã³ã¯è² è·ã«å¿ããŠå€æŽã§ããŸãããéåžžã¯ããã©ã«ãã®ãªãã·ã§ã³ã䜿çšããŸãã S3 å§çž®ã¯å©çšã§ããŸããããORC ã¯ããã©ã«ãã§ãã€ãã£ãå§çž®ã䜿çšããããšã«æ³šæããŠãã ããã
æµæ¢
ãã°ã®ä¿åãšåä¿¡ãæ§æããã®ã§ãéä¿¡ãæ§æããå¿
èŠããããŸãã æã
ã¯äœ¿çšããã ãã
ãŸããfluent.conf èšå®ãã¡ã€ã«ãå¿ èŠã§ãã ãããäœæããŠãœãŒã¹ãè¿œå ããŸãã
ããŒã24224
ãã€ã³ã0.0.0.0
ããã§ãFluentd ãµãŒããŒãèµ·åã§ããããã«ãªããŸããã ããé«åºŠãªæ§æãå¿
èŠãªå Žåã¯ã次ã®ãµã€ãã«ã¢ã¯ã»ã¹ããŠãã ããã
$ 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
ãã®æ§æã§ã¯ãã¹ã䜿çšããŸã /fluentd/log
éä¿¡åã«ãã°ããã£ãã·ã¥ããŸãã ãããè¡ããªããŠã倧äžå€«ã§ãããåèµ·åãããšã骚ã®æããäœæ¥ã§ãã£ãã·ã¥ããããã¹ãŠã倱ãããå¯èœæ§ããããŸãã ä»»æã®ããŒãã䜿çšããããšãã§ããŸãã24224 ãããã©ã«ãã® Fluentd ããŒãã§ãã
Fluentd ãå®è¡ãããŠããã®ã§ãããã« Nginx ãã°ãéä¿¡ã§ããŸãã éåžžãNginx ã Docker ã³ã³ãããŒã§å®è¡ããŸãããã®å ŽåãDocker ã«ã¯ 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
Nginx ãå¥ã®æ¹æ³ã§å®è¡ããå Žåã¯ããã° ãã¡ã€ã«ã䜿çšã§ããŸããFluentd ã«ã¯ã
äžèšã§èšå®ãããã°è§£æã Fluent èšå®ã«è¿œå ããŠã¿ãŸãããã
<filter YOUR-NGINX-TAG.*>
@type parser
key_name log
emit_invalid_record_to_error false
<parse>
@type json
</parse>
</filter>
ãããŠã次ã䜿çšããŠãã°ãKinesisã«éä¿¡ããŸã
<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>
ã¢ãã
ãã¹ãŠãæ£ããèšå®ãããŠããå Žåã¯ããã°ãããããš (ããã©ã«ãã§ã¯ãKinesis ã¯åä¿¡ããŒã¿ã 10 åããšã«èšé²ããŸã)ãS3 ã«ãã°ãã¡ã€ã«ã衚瀺ãããŸãã Kinesis Firehose ã®ãã¢ãã¿ãªã³ã°ãã¡ãã¥ãŒã§ã¯ãS3 ã«èšé²ãããŠããããŒã¿ã®éãšãšã©ãŒã確èªã§ããŸãã S3 ãã±ãããžã®æžã蟌ã¿ã¢ã¯ã»ã¹ã Kinesis ããŒã«ã«ä»äžããããšãå¿ããªãã§ãã ããã Kinesis ãäœãã解æã§ããªãã£ãå Žåãåããã±ããã«ãšã©ãŒãè¿œå ããŸãã
ããã§ãAthena ã§ããŒã¿ã衚瀺ã§ããããã«ãªããŸããã ãšã©ãŒãè¿ããææ°ã®ãªã¯ãšã¹ããèŠã€ããŠã¿ãŸãããã
SELECT * FROM "db_name"."table_name" WHERE status > 499 ORDER BY created_at DESC limit 10;
ãªã¯ãšã¹ãããšã«ãã¹ãŠã®ã¬ã³ãŒããã¹ãã£ã³ãã
ããã§ãã°ãåŠçãããORC ã® S3 ã«ä¿åãããå§çž®ãããŠåæã§ããããã«ãªããŸããã Kinesis Firehose ã§ã¯ãæéããšã«ãã£ã¬ã¯ããªã«æŽçããããšãã§ããŸããã ãã ããããŒãã«ãããŒãã£ã·ã§ã³åãããŠããªãéããAthena ã¯ããŸããªäŸå€ãé€ããŠããã¹ãŠã®ãªã¯ãšã¹ãã§åžžæããŒã¿ãèªã¿èŸŒã¿ãŸãã ããã¯æ¬¡ã® XNUMX ã€ã®çç±ãã倧ããªåé¡ã§ãã
- ããŒã¿éã¯åžžã«å¢å ããŠãããã¯ãšãªã®é床ãé ããªããŸãã
- Athena ã¯ã¹ãã£ã³ãããããŒã¿ã®éã«åºã¥ããŠèª²éããããªã¯ãšã¹ãããšã«æäœ 10 MB ã䜿çšãããŸãã
ãããä¿®æ£ããã«ã¯ãAWS Glue Crawler ã䜿çšããŸããããã¯ãS3 å ã®ããŒã¿ãã¯ããŒã«ããããŒãã£ã·ã§ã³æ å ±ã Glue Metastore ã«æžã蟌ã¿ãŸãã ããã«ãããAthena ã«ã¯ãšãªãå®è¡ãããšãã«ããŒãã£ã·ã§ã³ããã£ã«ã¿ãŒãšããŠäœ¿çšã§ããããã«ãªããã¯ãšãªã§æå®ããããã£ã¬ã¯ããªã®ã¿ãã¹ãã£ã³ãããŸãã
Amazon Glue ã¯ããŒã©ãŒã®ã»ããã¢ãã
Amazon Glue Crawler ã¯ãS3 ãã±ããå ã®ãã¹ãŠã®ããŒã¿ãã¹ãã£ã³ããããŒãã£ã·ã§ã³ãå«ãããŒãã«ãäœæããŸãã AWS Glue ã³ã³ãœãŒã«ãã Glue ã¯ããŒã©ãŒãäœæããããŒã¿ãä¿åãããã±ãããè¿œå ããŸãã è€æ°ã®ãã±ããã«å¯Ÿã㊠XNUMX ã€ã®ã¯ããŒã©ãŒã䜿çšã§ããŸãããã®å Žåãæå®ãããããŒã¿ããŒã¹ã«ãã±ããã®ååãšäžèŽããååã®ããŒãã«ãäœæãããŸãã ãã®ããŒã¿ãå®æçã«äœ¿çšããäºå®ãããå Žåã¯ãããŒãºã«åãã㊠Crawler ã®èµ·åã¹ã±ãžã¥ãŒã«ãæ§æããŠãã ããã ãã¹ãŠã®ããŒãã«ã«å¯Ÿã㊠XNUMX ã€ã®ã¯ããŒã©ãŒã䜿çšããXNUMX æéããšã«å®è¡ããŸãã
ããŒãã£ã·ã§ã³åãããããŒãã«
ã¯ããŒã©ãŒãåããŠèµ·åãããšãã¹ãã£ã³ãããåãã±ããã®ããŒãã«ãèšå®ã§æå®ãããããŒã¿ããŒã¹ã«è¡šç€ºãããŸãã Athena ã³ã³ãœãŒã«ãéããNginx ãã°ãå«ãŸããããŒãã«ãèŠã€ããŸãã äœããèªãã§ã¿ãŸããã:
SELECT * FROM "default"."part_demo_kinesis_bucket"
WHERE(
partition_0 = '2019' AND
partition_1 = '04' AND
partition_2 = '08' AND
partition_3 = '06'
);
ãã®ã¯ãšãªã¯ã6 幎 7 æ 8 æ¥ã®åå 2019 æããåå XNUMX æãŸã§ã«åä¿¡ãããã¹ãŠã®ã¬ã³ãŒããéžæããŸãã ããããããã¯ãããŒãã£ã·ã§ã³åãããŠããªãããŒãã«ããåã«èªã¿åããããã©ãã ãå¹ççã§ãããã? åãã¬ã³ãŒããèŠã€ããŠéžæããã¿ã€ã ã¹ã¿ã³ãã§ãã£ã«ã¿ãŒããŠã¿ãŸãããã
ããã 3.59 é±éã®ãã°ãå«ãããŒã¿ã»ããäžã® 244.34 ç§ãXNUMX ã¡ã¬ãã€ãã®ããŒã¿ã ããŒãã£ã·ã§ã³ããšã«ãã£ã«ã¿ãŒãè©ŠããŠã¿ãŸãããã
å°ãé«éã§ãããæãéèŠãªã®ã¯ãããŒã¿éãããã 1.23 ã¡ã¬ãã€ãã§ããããšã§ãã äŸ¡æ Œèšå®ã«ãªã¯ãšã¹ããããæäœ 10 ã¡ã¬ãã€ãããªããã°ãã¯ããã«å®ããªããŸãã ããããããã§ãã¯ããã«åªããŠããã倧èŠæš¡ãªããŒã¿ã»ããã§ã¯ãã®éãã¯ããã«å°è±¡çã«ãªããŸãã
Cube.js ã䜿çšããããã·ã¥ããŒãã®æ§ç¯
ããã·ã¥ããŒããçµã¿ç«ãŠãã«ã¯ãCube.js åæãã¬ãŒã ã¯ãŒã¯ã䜿çšããŸãã éåžžã«å€ãã®æ©èœããããŸãããç§ãã¡ã泚ç®ããŠããã®ã¯ XNUMX ã€ã§ããããŒãã£ã·ã§ã³ ãã£ã«ã¿ãŒãšããŒã¿ã®äºåéèšãèªåçã«äœ¿çšããæ©èœã§ãã ããŒã¿ã¹ããŒãã䜿çšããŸã
æ°ãã Cube.js ã¢ããªã±ãŒã·ã§ã³ãäœæããŸãããã ãã§ã« AWS ã¹ã¿ãã¯ã䜿çšããŠããããããããã€ã¡ã³ãã« Lambda ã䜿çšããã®ã¯è«ççã§ãã Heroku ãŸã㯠Docker 㧠Cube.js ããã¯ãšã³ãããã¹ãããäºå®ãããå Žåã¯ãExpress ãã³ãã¬ãŒããçæã«äœ¿çšã§ããŸãã ããã¥ã¡ã³ãã§ã¯ãã®ä»ã«ã€ããŠèª¬æããŠããŸã
$ npm install -g cubejs-cli
$ cubejs create nginx-log-analytics -t serverless -d athena
ç°å¢å€æ°ã¯ãcube.js ã§ããŒã¿ããŒã¹ ã¢ã¯ã»ã¹ãæ§æããããã«äœ¿çšãããŸãã ãžã§ãã¬ãŒã¿ãŒã¯ãããŒãæå®ã§ãã .env ãã¡ã€ã«ãäœæããŸãã
ä»å¿
èŠãªã®ã¯
ãã£ã¬ã¯ããªå
schema
ããã¡ã€ã«ãäœæããŸã Logs.js
ã nginx ã®ããŒã¿ ã¢ãã«ã®äŸã次ã«ç€ºããŸãã
ååŒã³ãŒã
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`
}
}
});
ããã§ã¯å€æ°ã䜿çšããŠããŸã
ãŸããããã·ã¥ããŒãã«è¡šç€ºããã¡ããªã¯ã¹ãšãã©ã¡ãŒã¿ãèšå®ããäºåéèšãæå®ããŸãã Cube.js ã¯ãäºåã«éèšãããããŒã¿ãå«ãè¿œå ã®ããŒãã«ãäœæããããŒã¿ãå°çãããšèªåçã«æŽæ°ããŸãã ããã«ãããã¯ãšãªãé«éåãããã ãã§ãªããAthena ã®äœ¿çšã³ã¹ããåæžãããŸãã
ãã®æ å ±ãããŒã¿ ã¹ããŒã ãã¡ã€ã«ã«è¿œå ããŸãããã
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`
)
}
}
}
ãã®ã¢ãã«ã§ã¯ã䜿çšããããã¹ãŠã®ã¡ããªã¯ã¹ã®ããŒã¿ãäºåã«éèšããæããšã«ããŒãã£ã·ã§ã³åå²ã䜿çšããå¿
èŠãããããšãæå®ããŸãã
ããã§ããã·ã¥ããŒããçµã¿ç«ãŠãããšãã§ããŸãïŒ
Cube.js ããã¯ãšã³ããæäŸãããã®
Cube.js ãµãŒããŒã¯ãªã¯ãšã¹ããåãå
¥ããŸãã
{
"measures": ["Logs.errorCount"],
"timeDimensions": [
{
"dimension": "Logs.createdAt",
"dateRange": ["2019-01-01", "2019-01-07"],
"granularity": "day"
}
]
}
Cube.js ã¯ã©ã€ã¢ã³ããš React ã³ã³ããŒãã³ã ã©ã€ãã©ãªã NPM çµç±ã§ã€ã³ã¹ããŒã«ããŸãããã
$ npm i --save @cubejs-client/core @cubejs-client/react
ã³ã³ããŒãã³ãã茞å
¥ããŠããŸã cubejs
О QueryRenderer
ããŒã¿ãããŠã³ããŒãããããã·ã¥ããŒããåéããã«ã¯:
ããã·ã¥ããŒãã³ãŒã
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>
);
}}
/>
)
}
ããã·ã¥ããŒãã®ãœãŒã¹ã¯æ¬¡ã®å Žæããå
¥æã§ããŸãã
åºæïŒ habr.com