higee / elastic Goto Github PK
View Code? Open in Web Editor NEWElastic Stack (6.2.4) 을 활용한 Dashboard 만들기 Project
Elastic Stack (6.2.4) 을 활용한 Dashboard 만들기 Project
{ "user": "jane" }
{ "user": "" }
{ "user": ["jane"] }
{ "user": ["jane", null ] }
{ "user": null }
{ "user": [] }
{ "user": [null] }
{ "foo": "bar" }
GET /_search
{
"query": {
"exists" : {
"field" : "user"
}
}
}
document | exists | missing | 비고 |
---|---|---|---|
{ "user": "jane" } |
o | "jane"이라는 non-null value 존재 | |
{ "user": "" } |
o | 빈 string은 non-null value | |
{ "user": ["jane"] } |
o | "jane"이라는 non-null value 존재 | |
{ "user": ["jane", null] } |
o | "jane"이라는 non-null value 존재 | |
{ "user": null} |
o | null만 존재 | |
{ "user": [] } |
o | 아무 값도 없으므로 null | |
{ "user": [null] } |
o | null만 존재 | |
{ "foo": "bar" } |
o | "user" field 자체가 없음 |
PUT test_index
{
"mappings": {
"test_type": {
"properties": {
"user": {
"type": "keyword",
"null_value": "NULL"
}
}
}
}
}
null
로 들어온 value를 NULL
이라는 string으로 변환document | exists | missing | 비고 |
---|---|---|---|
{ "user": "jane" } |
o | "jane"이라는 non-null value 존재 | |
{ "user": "" } |
o | 빈 string은 non-null value | |
{ "user": ["jane"] } |
o | "jane"이라는 non-null value 존재 | |
{ "user": ["jane", null] } |
o | "jane"이라는 non-null value 존재 | |
{ "user": null} |
o | null -> NULL 변환되어 non-null value 존재 |
|
{ "user": [] } |
o | 명시적으로 null으로 들어온 게 없으므로 null | |
{ "user": [null] } |
o | null -> NULL 변환되어 non-null value 존재 |
|
{ "foo": "bar" } |
o | "user" field 자체가 없음 |
위에서 만든 test_index에 데이터를 넣어보자
null
PUT test_index/test_type/1
{
"user": null
}
null
PUT test_index/test_type/2
{
"user": []
}
전체 Documents 확인
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 2,
"max_score": 1,
"hits": [
{
"_index": "test_index",
"_type": "test_type",
"_id": "2",
"_score": 1,
"_source": {
"user": []
}
},
{
"_index": "test_index",
"_type": "test_type",
"_id": "1",
"_score": 1,
"_source": {
"user": null
}
}
]
}
}
query
GET test_index/_search
{
"query": {
"term": {
"user": "NULL"
}
}
}
결과 : 위에서 _id가 1번인 document만 출력된다
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 0.2876821,
"hits": [
{
"_index": "test_index",
"_type": "test_type",
"_id": "1",
"_score": 0.2876821,
"_source": {
"user": null
}
}
]
}
}
non-null value가 연속해서 오지 않았기에
특정 interval 동안 documents count가 20개 이상이면 녹색, 20개 미만이면 분홍색으로 line graph 생성
.es().label(20이하),
.es().if(gte, 20, .es(), null).label(20이상)
.es().if(lt, 20, .es(), null).label(20이하)
.es().if(gte, 20, .es(), null).label(20이상)
unicode value
로 정렬한다
unicode value
라고 가정Unicode Value (Range) | Unicode Name |
---|---|
U+1100 - U+11FF | 한글 |
U+0061 - U+007A | 영어 소문자 |
U+0041 - U+005A | 영어 대문자 |
U+0030 - U+0039 | 숫자 |
구매사이트
라는 field mapping을 아래와 같이 했다고 하자PUT shopping
{
"mappings": {
"shopping": {
"properties": {
"구매사이트": {
"type": "keyword"
}
}
}
}
}
구매사이트
field의 terms를 기준으로 ascending으로 정렬해보자네
Management
- Advanced Settings
- xpack.reporting.csv.maxSizeBytes
에서 변경 가능$ bin/logstash-plugin install logstash-output-csv
input {
elasticsearch {
hosts => "localhost:9200" # 실제 데이터가 들어있는 host로 변경 필요
index => "shopping" # csv로 추출하려는 데이터가 담긴 index
query => '{ # 선택한 index에서 어떤 데이터를 추출할 건지 query DSL 작성
"query" : {
"match_all" : {
}
}
}'
}
}
output {
csv {
fields => ["주문시간", "물건좌표"] # csv 파일에 필요한 field 입력
path => "/home/ec2-user/shopping-logstash.csv" # 파일이 저장될 경로 입력
}
}
/home/ec2-user/shopping-logstash.csv
에서 가능$ pip install elasticsearch==6.2.0
import csv
from elasticsearch import Elasticsearch
es = Elasticsearch(hosts=["localhost:9200"])
data = es.search(
index="shopping",
size=10000,
body={
"query" : {
"match_all" : {
}
}
}
)
csv_columns = ['상품가격', '상품분류', '예약여부', '구매사이트', '수령시간', \
'물건좌표', '주문시간', '결제카드', '고객ip', '판매자평점', '배송메모', \
'고객주소_시도', '상품개수', '접수번호', '고객나이', '고객성별'
]
csv_file = '/home/ec2-user/shopping-python.csv'
with open(csv_file, 'w') as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=csv_columns)
writer.writeheader()
for document in [x['_source'] for x in data['hits']['hits']]:
writer.writerow(document)
/home/ec2-user/shopping-python.csv
에서 가능Data Table
Visualization을 이용해 원하는 형태로 테이블 생성Export
옆에 Raw
또는 Formatted
중 선택해서 출력우선 Elastic Stack 측면에서 파일을 전송하는 부분은 logstash인데, 현재 공식 버전 (6.1)까지 excel 지원은 없다. csv의 경우 file
input plugin 활용해서 전송한다.
꼭 excel data를 이용해야 한다면 다음과 같은 방법이 있을 것이다.
Elasticsearch client(Python, Java 등) 활용해서 직접 elasticsearch에 insert하기
excel을 직접 csv format으로 변환해서 logstash 활용하기
community plugin 활용하기
주요 기능은 다음과 같다
상세한 기능 및 가격의 정확한 내용은 공식 홈페이지에서 확인 가능하다.
가능하다
mysql> select * from test;
>>>
+----+-------+
| id | name |
+----+-------+
| 1 | hello |
| 2 | NULL |
+----+-------+
name
field가 NULL
이면 event를 drop 해보자logstash configuration
$ cat test.conf
>>>
input {
jdbc {
jdbc_validate_connection => true
jdbc_connection_string => "jdbc:mysql://52.78.134.20:3306/fc"
jdbc_user => "fc"
jdbc_password => "fc"
jdbc_driver_library => "/usr/share/logstash/driver/mysql-connector-java-5.1.36-bin.jar"
jdbc_driver_class => "com.mysql.jdbc.Driver"
statement => "SELECT * FROM test"
}
}
filter {
if ![name] {
drop {}
}
}
output {
stdout {
}
}
결과
{
"name" => "hello",
"id" => 1,
"@timestamp" => 2018-07-21T14:31:34.624Z,
"@version" => "1"
}
name
field가 NULL
이면 특정한 값으로 대체하자logstash configuration
$ cat test.conf
>>>
input {
jdbc {
jdbc_validate_connection => true
jdbc_connection_string => "jdbc:mysql://52.78.134.20:3306/fc"
jdbc_user => "fc"
jdbc_password => "fc"
jdbc_driver_library => "/usr/share/logstash/driver/mysql-connector-java-5.1.36-bin.jar"
jdbc_driver_class => "com.mysql.jdbc.Driver"
statement => "SELECT * FROM test"
}
}
filter {
ruby {
code => "event.set('name', 'this is null') if event.get('name').nil?"
}
}
output {
stdout {
}
}
결과
{
"id" => 1,
"name" => "hello",
"@version" => "1",
"@timestamp" => 2018-07-21T14:36:31.711Z
}
{
"id" => 2,
"name" => "this is null",
"@version" => "1",
"@timestamp" => 2018-07-21T14:36:31.757Z
}
NULL
여부를 확인하고 NULL
인 field만 제거하자logstash configuration
$ cat test.conf
>>>
input {
jdbc {
jdbc_validate_connection => true
jdbc_connection_string => "jdbc:mysql://52.78.134.20:3306/fc"
jdbc_user => "fc"
jdbc_password => "fc"
jdbc_driver_library => "/usr/share/logstash/driver/mysql-connector-java-5.1.36-bin.jar"
jdbc_driver_class => "com.mysql.jdbc.Driver"
statement => "SELECT * FROM test"
}
}
filter {
ruby {
code => "
hash = event.to_hash
hash.each do |k,v|
if v == nil
event.remove(k)
end
end
"
}
}
output {
stdout {
}
}
결과
{
"@timestamp" => 2018-07-21T14:39:02.454Z,
"@version" => "1",
"id" => 1,
"name" => "hello"
}
{
"@timestamp" => 2018-07-21T14:39:02.505Z,
"@version" => "1",
"id" => 2
}
input plugin 마다 기준이 상이한 것 같다
데이터 (message.txt)
$ wc -lm message.txt
>>>
1 276481 message.txt
logstash configuration
input {
file {
path => "/usr/share/logstash/message.txt"
start_position => "beginning"
}
}
output {
stdout {
}
}
결과 : 위와 같이 27만 character로 된 single message도 정상적으로 처리 가능
logstash configuration : buffer size로 제한
input {
udp {
port => 5000
buffer_size => 100
}
}
output {
stdout {
}
}
log 전송
$ logger -n 127.0.0.1 -P 5000 "Test Message : hello world"
$ logger -n 127.0.0.1 -P 5000 "Test Message : hello world. Welcome to higee/elastic repository"
$ logger -n 127.0.0.1 -P 5000 "Test Message : hello world. Welcome to higee/elastic repository. In this course, we get to learn the basics of elastic stack"
결과 : buffer size를 초과하는 message는 bugger size 이상 부분부터 내용이 잘리는 거 확인
2018-07-22T02:53:33.719Z 127.0.0.1 <5>Jul 22 11:53:33 ec2-user: Test Message : hello world
2018-07-22T02:53:57.301Z 127.0.0.1 <5>Jul 22 11:53:57 ec2-user: Test Message : hello world. Welcome to higee/elastic repository
2018-07-22T02:54:13.635Z 127.0.0.1 <5>Jul 22 11:54:13 ec2-user: Test Message : hello world. Welcome to higee/elastic repository. In thi
가능하다
우선 Aggregation 차원에서 한 번에 가능하지는 않다.
가령 아래와 같은 데이터가 있다고 가정하자.
(단, shopping
index 특성상 매출 = "상품가격"의 합
으로 구한다고 하자)
날짜 | 매출 | 개수 |
---|---|---|
2018.01.01 | 423,000 | 23 |
2018.01.02 | 477,000 | 26 |
2018.01.03 | 394,000 | 22 |
2018.01.04 | 465,000 | 27 |
2018.01.05 | 352,000 | 21 |
2018.01.06 | 361,000 | 24 |
여기서 매출
의 누적평균을 구하려면 어떻게 해야 할까?
누적평균이 무엇을 의미하는지 생각해보면 구현
할 수는 있다.
가령 누적평균을 일별로 "매출"을 누적하여 더한 값을 "개수"를 누적하여 더한 값으로 나눠준 값
이라고 정의하자
날짜 | 매출 | 개수 | 누적 매출 | 누적 개수 |
---|---|---|---|---|
2018.01.01 | 423,000 | 23 | 423,000 | 23 |
2018.01.02 | 477,000 | 26 | 900,000 | 49 |
2018.01.03 | 394,000 | 22 | 1,294,000 | 71 |
2018.01.04 | 465,000 | 27 | 1,759,000 | 98 |
2018.01.05 | 352,000 | 21 | 2,111,000 | 119 |
2018.01.06 | 361,000 | 24 | 2,472,000 | 143 |
위의 표를 이용하면 일별 누적 평균은 아래와 같게 구할 수 있다.
날짜 | 매출 | 개수 | 누적 매출 | 누적 개수 | 누적 평균 |
---|---|---|---|---|---|
2018.01.01 | 423,000 | 23 | 423,000 | 23 | 18391 |
2018.01.02 | 477,000 | 26 | 900,000 | 49 | 18367 |
2018.01.03 | 394,000 | 22 | 1,294,000 | 71 | 18225 |
2018.01.04 | 465,000 | 27 | 1,759,000 | 98 | 17948 |
2018.01.05 | 352,000 | 21 | 2,111,000 | 119 | 17739 |
2018.01.06 | 361,000 | 24 | 2,472,000 | 143 | 17286 |
그렇다면 이러한 값을 어떻게 Kibana 상에서 시각화 할 수 있을까?
가장 쉬운 방법으로 Timelion
을 이용할 수 있다.
.es(index=shopping, timefield=주문시간, metric=sum:상품가격).cusum().divide(.es(index=shopping, timefield=주문시간).cusum()).label(누적평균)
다른 그래프와 같이 보면 아래와 같다
.es(index=shopping, timefield=주문시간, metric=sum:상품가격).cusum().label("매출 누적합").yaxis(yaxis=2, label='매출 누적합', min=0),
.es(index=shopping, timefield=주문시간, metric=sum:상품가격).cusum().divide(.es(index=shopping, timefield=주문시간).cusum()).label("누적 평균").yaxis(yaxis=1, min=0, max=30000, label='누적 평균 및 누적 개수').points(),
.es(index=shopping, timefield=주문시간).cusum().label("누적 개수").bars()
입력은 가능하나 sorting 등은 사용할 수 없다
2018-07-05 04:24:56.170243
를 색인한다고 하자PUT default-format
{
"mappings": {
"_doc": {
"properties": {
"@timestamp" : {
"type" : "date"
}
}
}
}
}
POST default-format/_doc
{
"@timestamp" : "2018-07-05 04:24:56.170243"
}
{
"error": {
"root_cause": [
{
"type": "mapper_parsing_exception",
"reason": "failed to parse [@timestamp]"
}
],
"type": "mapper_parsing_exception",
"reason": "failed to parse [@timestamp]",
"caused_by": {
"type": "illegal_argument_exception",
"reason": "Invalid format: \"2018-07-05 04:24:56.170243\" is malformed at \" 04:24:56.170243\""
}
},
"status": 400
}
PUT customized-format
{
"mappings": {
"_doc": {
"properties": {
"@timestamp" : {
"type" : "date",
"format" : "yyyy-MM-dd HH:mm:ss.SSSSSS"
}
}
}
}
}
POST customized-format/_doc
{
"@timestamp" : "2018-07-05 04:24:56.170243"
}
{
"_index": "customized-format",
"_type": "_doc",
"_id": "55TFZmQByNsCKuKnb22Y",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}
POST customized-format/_doc
{
"@timestamp" : "2018-07-05 04:24:56.170243"
}
POST customized-format/_doc
{
"@timestamp" : "2018-07-05 04:24:56.170244"
}
검증
sort
를 해도 millisecond 이하로는 대소를 비교할 수 없다GET customized-format/_search
{
"query": {
"match_all": {
}
},
"sort": [
{
"@timestamp": {
"order": "desc"
}
}
]
}
sort
값이 같은 걸 확인할 수 있다{
"took": 0,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 2,
"max_score": null,
"hits": [
{
"_index": "customized-format",
"_type": "_doc",
"_id": "CZTJZmQByNsCKuKnu25J",
"_score": null,
"_source": {
"@timestamp": "2018-07-05 04:24:56.170244"
},
"sort": [
1530764696170
]
},
{
"_index": "customized-format",
"_type": "_doc",
"_id": "B5TJZmQByNsCKuKnsG6f",
"_score": null,
"_source": {
"@timestamp": "2018-07-05 04:24:56.170243"
},
"sort": [
1530764696170
]
}
]
}
}
가능하다
input {
file {
path => "/usr/share/logstash/data/titanic.csv"
start_position => "beginning"
sincedb_path => "/dev/null"
}
}
output {
stdout {
}
}
{
"host" => "65778ce05d75",
"message" => "49,Samaan,0,3,male,29.69911765,2,0,2662,21.6792,C",
"@version" => "1",
"path" => "/usr/share/logstash/data/titanic.csv",
"@timestamp" => 2018-07-25T16:20:08.338Z
}
input {
file {
path => "/usr/share/logstash/data/titanic.csv"
start_position => "beginning"
sincedb_path => "/dev/null"
}
}
filter {
mutate {
split => { "path" => "." }
}
}
output {
stdout {
}
}
{
"host" => "65778ce05d75",
"message" => "49,Samaan,0,3,male,29.69911765,2,0,2662,21.6792,C",
"@version" => "1",
"path" => [
[0] "/usr/share/logstash/data/titanic",
[1] "csv"
],
"@timestamp" => 2018-07-25T16:20:10.413Z
}
input {
file {
path => "/usr/share/logstash/data/titanic.csv"
start_position => "beginning"
sincedb_path => "/dev/null"
}
}
filter {
mutate {
split => { "path" => "." }
add_field => { "filename_extension" => "%{path[-1]}"}
}
}
output {
stdout {
}
}
{
"@version" => "1",
"path" => [
[0] "/usr/share/logstash/data/titanic",
[1] "csv"
],
"filename_extension" => "csv",
"host" => "65778ce05d75",
"message" => "47,Lennon,0,3,male,29.69911765,1,0,370371,15.5,Q",
"@timestamp" => 2018-07-25T16:27:42.249Z
}
가능하다
$ cat /usr/share/logstash/password
>>>
fc
input {
jdbc {
jdbc_validate_connection => true
jdbc_connection_string => "jdbc:mysql://52.78.134.20:3306/fc"
jdbc_user => "fc"
jdbc_password_filepath => "/usr/share/logstash/password"
jdbc_driver_library => "/usr/share/logstash/driver/mysql-connector-java-5.1.36-bin.jar"
jdbc_driver_class => "com.mysql.jdbc.Driver"
statement => "SELECT * FROM test"
}
}
output {
stdout {
}
}
$ export password=fc
input {
jdbc {
jdbc_validate_connection => true
jdbc_connection_string => "jdbc:mysql://52.78.134.20:3306/fc"
jdbc_user => "fc"
jdbc_password => "${password}"
jdbc_driver_library => "/usr/share/logstash/driver/mysql-connector-java-5.1.36-bin.jar"
jdbc_driver_class => "com.mysql.jdbc.Driver"
statement => "SELECT * FROM test"
}
}
output {
stdout {
}
}
Keystore 접근용 비밀번호 생성
$ export LOGSTASH_KEYSTORE_PASS=higee
Keystore 생성
$ bin/logstash-keystore create
>>>
Created Logstash keystore at /usr/share/logstash/config/logstash.keystore
Keystore에 추가
$ bin/logstash-keystore add jdbc_password
>>>
Enter value for jdbc_password: # 여기에 비밀번호 입력 후 엔터
Added 'jdbc_password' to the Logstash keystore.
logstash configuration
input {
jdbc {
jdbc_validate_connection => true
jdbc_connection_string => "jdbc:mysql://52.78.134.20:3306/fc"
jdbc_user => "fc"
jdbc_password => "${jdbc_password}"
jdbc_driver_library => "/usr/share/logstash/driver/mysql-connector-java-5.1.36-bin.jar"
jdbc_driver_class => "com.mysql.jdbc.Driver"
statement => "SELECT * FROM test"
}
}
output {
stdout {
}
}
기본적으로 사용될 때 Scripted Field가 사용될 때 실행된다. 다만 Scripted Field를 호출하는 방식에 따라 차이가 있다.
데이터 타입1 | 데이터 타입2 | 가능 여부 |
---|---|---|
string | text | ✕ |
string | keyword | ◎ |
numeric | long | ◎ |
numeric | integer | ◎ |
numeric | short | ◎ |
numeric | byte | ◎ |
numeric | double | ◎ |
numeric | float | ◎ |
numeric | half-float | ◎ |
numeric | scaled-float | ◎ |
date | date | ◎ |
boolean | boolean | ◎ |
geo_point | geo_point | ✕ |
array | ✕ | |
nested | ✕ | |
object | ◎ |
GET test/_search
{
"sort": [
{
"_geo_distance": {
"geo_point": "37.11, 128.12",
"order": "asc",
"unit": "km"
}
}
]
}
GET test/_search
{
"sort": {
"array.가격": {
"order": "asc",
"mode": "avg"
}
}
}
GET test/_search
{
"sort": [
{
"nested.가격": {
"mode": "avg",
"order": "asc",
"nested": {
"path": "nested"
}
}
}
]
}
PUT test
{
"mappings": {
"test": {
"properties": {
"date" : {
"type" : "date"
},
"geo_point" : {
"type": "geo_point"
},
"text": {
"type": "text"
},
"long": {
"type": "long"
},
"integer": {
"type": "integer"
},
"short": {
"type": "short"
},
"byte": {
"type": "byte"
},
"double": {
"type": "double"
},
"float": {
"type": "float"
},
"half-float": {
"type": "half_float"
},
"scaled-float": {
"type": "scaled_float",
"scaling_factor": 100
},
"boolean": {
"type": "boolean"
},
"array": {
"properties": {
"가격": {
"type": "integer"
}
}
},
"nested": {
"type": "nested",
"properties": {
"가격": {
"type": "integer"
}
}
},
"object": {
"properties": {
"가격": {
"type": "integer"
}
}
}
}
}
}
}
PUT test/test/1
{
"date" : "2018-08-18T16:40:33",
"text": "hello world",
"long": 1,
"geo_point" : "37.11, 128.11",
"integer": 1,
"short": 1,
"byte": 1,
"double": 0.1,
"float": 0.1,
"half-float": 0.1,
"scaled_float": 0.1,
"boolean": true,
"object": {
"가격": 1
},
"nested": [
{
"가격": 100
},
{
"가격": 200
}
],
"array" : [
{
"가격": 100
},
{
"가격": 200
}
]
}
Boolean Operator를 통해 가능하다
&&
if (doc['상품개수'].value < 3 && doc['상품가격'].value < 15000){
return "저소비"
}
return "과소비"
||
if (doc['상품개수'].value < 3 || doc['상품가격'].value < 15000){
return "저소비"
}
return "과소비"
!
if (doc['상품개수'].value < 3 && !(doc['상품가격'].value < 15000)){
return "저소비"
}
return "과소비"
최신 version 결과만 조회할 수 있을 뿐 이전 version 값을 조회할 수는 없다
PUT test/test/1
{
"name" : "higee"
}
GET test/test/1
>>>
{
"_index": "test",
"_type": "test",
"_id": "1",
"_version": 1,
"found": true,
"_source": {
"name": "higee"
}
}
PUT test/test/1
{
"name" : "higee/elastic"
}
GET test/test/1
>>>
{
"_index": "test",
"_type": "test",
"_id": "1",
"_version": 2,
"found": true,
"_source": {
"name": "higee/elastic"
}
}
색인(indexing)이 되기 전이라면 가능하다
PUT _template/test
{
"index_patterns": "test*",
"mappings": {
"test": {
"properties": {
"name": {
"type": "keyword"
}
}
}
}
}
POST test1/test
{
"name" : "higee"
}
PUT _template/test
{
"index_patterns": "test*",
"mappings": {
"test": {
"properties": {
"name": {
"type": "text"
}
}
}
}
}
POST test1/test
{
"name" : "higee/elastic"
}
GET test1/_mapping
>>>
{
"test1": {
"mappings": {
"test": {
"properties": {
"name": {
"type": "keyword"
}
}
}
}
}
}
PUT _template/test
{
"index_patterns": "test*",
"mappings": {
"test": {
"properties": {
"name": {
"type": "keyword"
}
}
}
}
}
PUT _template/test
{
"index_patterns": "test*",
"mappings": {
"test": {
"properties": {
"name": {
"type": "text"
}
}
}
}
}
POST test2/test
{
"name" : "higee"
}
GET test2/_mapping
>>>
{
"test1": {
"mappings": {
"test": {
"properties": {
"name": {
"type": "text"
}
}
}
}
}
}
GET /_template/test
>>>
{
"test": {
"order": 0,
"index_patterns": [
"test*"
],
"settings": {},
"mappings": {
"test": {
"properties": {
"name": {
"type": "text"
}
}
}
},
"aliases": {}
}
}
titanic.csv
)가 있다고 하자Index, Name, Survival, Pclass, Sex, Age, SibSp, Parch, Ticket, Fare, Embarked
1,Braund,0,3,male,22,1,0,A/5 21171,7.25,S
2,Cumings,1,1,female,38,1,0,PC 17599,71.2833,C
3,Heikkinen,1,3,female,26,0,0,STON/O2. 3101282,7.925,S
csv.conf
)을 설정한다input {
file {
path => "/home/ec2-user/fc/logstash-5.6.4/sample/titanic.csv"
}
}
filter {
csv {
separator => ","
autodetect_column_names => true
}
}
output {
stdout {
codec => rubydebug
}
}
$ bin/logstash -f csv.conf
우선 아래와 같은 date.log
파일이 있다고 하자.
2018-02-12T16:03:38 Ben
2018-02-13T03:25:31 John
2018-02-14T13:31:11 Leo
이 때 아래와 같은 test.conf
로 실행해보자
input {
file {
path => "/usr/share/logstash/data/date.log"
start_position => "beginning"
sincedb_path => "/usr/share/logstsah/test.db"
}
}
filter {
mutate {
remove_field => ["@version", "host", "path", "@timestamp"]
}
}
output {
stdout {
codec => rubydebug
}
}
그러면 아래와 같은 결과가 나온다
{
"message" => "2018-02-12T16:03:38 Ben"
}
{
"message" => "2018-02-13T03:25:31 John"
}
{
"message" => "2018-02-14T13:31:11 Leo"
}
sincedb로 지정했던 test.db
내부를 보자.
$ cat test.db
>>>
2331699 0 51713 73
이 때 마지막 73
이 current_byte_offset
에 해당하는 값으로 현재 어디까지 조회했는지에 대한 정보다.
다만 이 값은 key, value 형태가 아니라 어디까지 봤는지에 대한 위치 정보 같은 값이므로 date.log
를 변경함에 따라 결과도 달라진다. 아래와 같은 조건으로 date.log
를 변경하면서 logstash 결과를 확인하자.
date.log
를 아래와 같이 수정해보자2018-02-12T16:03:38 Ben
2018-02-13T03:25:31 John
2018-02-14T13:31:11 Leo
2018-02-15T23:31:11 Tom
그리고 실행하자
$ bin/logstash -f test.conf
결과는 아래와 같다 : 생각한대로 새로 추가된 내용만 출력됐다
{
"message" => "2018-02-15T23:31:11 Tom"
}
sincedb
를 확인해보자 : 4번째 field만 변경된 걸 확인할 수 있다$ cat test.db
>>>
2331699 0 51713 97
$ echo '2331699 0 51713 73' > test.db
$ cat test.db
>>>
2331699 0 51713 73
date.log
를 아래와 같이 수정해보자2018-02-11T16:03:38 Jay
2018-02-12T16:03:38 Ben
2018-02-13T03:25:31 John
2018-02-14T13:31:11 Leo
그리고 실행하자
$ bin/logstash -f test.conf
결과는 아래와 같다
{
"message" => "2018-02-14T13:31:11 Leo"
}
sincedb
를 확인하자$ cat test.db
>>>
2331699 0 51713 97
결과해석 : 아래와 같이 sincedb
는 데이터 값이 아니라 위치
로 기억하므로 의도한 대로 출력되지 않는다
2018-02-12T16:03:38 Ben
2018-02-13T03:25:31 John
2018-02-14T13:31:11 Leo # <<< 세번째 라인 끝까지 읽었다고 기록
2018-02-11T16:03:38 Jay # <<< 새로 추가된 라인
2018-02-12T16:03:38 Ben
2018-02-13T03:25:31 John # <<< sincedb는 여기까지 읽었다고 기록
2018-02-14T13:31:11 Leo
$ echo '2331699 0 51713 73' > test.db
$ cat test.db
>>>
2331699 0 51713 73
date.log
를 아래와 같이 수정해보자2018-02-12T16:03:38 Ben
2018-02-13T03:25:31 John
2018-02-14T13:31:11 NEW
그리고 실행하자
$ bin/logstash -f test.conf
결과는 아래와 같다
sincedb
를 확인하자$ cat test.db
>>>
2331699 0 51713 73
$ echo '2331699 0 51713 73' > test.db
$ cat test.db
>>>
2331699 0 51713 73
date.log
를 아래와 같이 수정해보자2018-02-12T16:03:38 Ben
2018-02-13T03:25:31 John
2018-02-14T13:31:11 NEWDATA
그리고 실행하자
$ bin/logstash -f test.conf
결과는 아래와 같다
{
"message" => "ATA"
}
sincedb
를 확인하자$ cat test.db
>>>
2331699 0 51713 77
Lucene Query Syntax를 이용해서 특정 phrase로 시작하는 Documents를 검색한다고 하자.
Field : nginx.access.geoip.country_name
조건 : "Republic of"로 시작하는 Documents
어떻게 하면 될까?
옵션1
nginx.access.geoip.country_name:Republic*of*
Republic of
로 시작하는 Documents는 모두 검색한다Republic_of
, Republicof
등으로 시작하는 Documents도 검색이 되어버린다옵션2
nginx.access.geoip.country_name:Republic\ of*
Republic of
로 시작하는 Documents만 검색한다네 적용됩니다.
PUT test_reindex/_mapping/test_reindex
{
"properties": {
"번호": {
"type": "integer"
},
"시간": {
"type": "date"
},
"이름": {
"type": "keyword"
},
"이메일": {
"type": "text"
}
}
}
PUT test_reindex/test_reindex/1
{
"번호" : 1,
"시간" : "2017-12-01",
"이름": "higee",
"이메일" : "[email protected]"
}
PUT _template/test_template
{
"template": "test_reindex_destination",
"mappings": {
"my_type": {
"properties": {
"번호": {
"type": "integer"
},
"시간": {
"type": "date"
},
"이름": {
"type": "keyword"
},
"이메일": {
"type": "text"
}
}
}
}
}
PUT test_reindex_destination
GET test_reindex_destination/_mapping
{
"test_reindex_destination": {
"mappings": {
"my_type": {
"properties": {
"번호": {
"type": "integer"
},
"시간": {
"type": "date"
},
"이름": {
"type": "keyword"
},
"이메일": {
"type": "text"
}
}
}
}
}
}
POST _reindex
{
"source": {
"index": "test_reindex"
},
"dest": {
"index": "test_reindex_destination"
}
}
GET test_reindex_destination/_search
{
"query": {
"match_all": {}
}
}
우선 편의상 아래와 같이 default 설정을 하고 시작한다.
index=nginx-*
timefield=@timestamp
.es()
.es().label(20이하),
.es().if(gte, 20, .es(), null).label(20이상)
.es().if(gte, 20, .es(), null).label(20이상),
.es().label(20이하)
가능하다
logstash configuraiton
input {
stdin {
}
}
filter {
ruby {
code => "
if event.get('message').length > 10
event.set('new message', event.get('message')[0..5])
event.remove('message')
event.tag('ruby filter activated')
end
"
}
}
output {
stdout {
}
}
테스트
$ hi
>>>
{
"message" => "hi",
"@timestamp" => 2018-07-22T05:12:15.331Z,
"host" => "69a61635b4d0",
"@version" => "1"
}
$ hello world
>>>
{
"@timestamp" => 2018-07-22T05:12:18.906Z,
"host" => "69a61635b4d0",
"tags" => [
[0] "ruby filter activated"
],
"@version" => "1",
"new message" => "hello "
}
logstash configuraiton
input {
stdin {
}
}
filter {
truncate {
fields => ["message"]
length_bytes => 10
}
}
output {
stdout {
}
}
테스트
$ hi
>>>
{
"host" => "69a61635b4d0",
"@version" => "1",
"@timestamp" => 2018-07-22T05:20:46.970Z,
"message" => "hi"
}
$ hello world
>>>
{
"host" => "69a61635b4d0",
"@version" => "1",
"@timestamp" => 2018-07-22T05:21:00.556Z,
"message" => "hello worl"
}
logstash configuraiton
short
tag 추가drop
input {
stdin {
}
}
filter {
range {
ranges => [
"message", 0, 10, "tag:short",
"message", 10, 20, "drop"
]
}
}
output {
stdout {
}
}
테스트
$ hi
>>>
{
"host" => "69a61635b4d0",
"tags" => [
[0] "short"
],
"message" => "hi",
"@timestamp" => 2018-07-22T05:48:21.098Z,
"@version" => "1"
}
$ hello world
아래 3개 정보가 모두 같은 경우에만 같게 인식한다.
다만 일반적으로 major/minor device number of the file system은 일정하니 inode에 집중하면 된다
기본적으로 linux file system에서 파일을 생성하면 아래와 같이 구분되어 저장된다
아래와 같이 파일을 생성한 후 inode를 보자
$ touch test1
$ ls -i test1
>>>
14434 test1
파일을 삭제하고 같은 이름으로 재생성하고 inode를 보자
$ rm test1
$ touch test1
$ ls -i test1
>>>
14434 test1
파일(test1)을 삭제하고, 다른 파일(test2)을 생성한 후에, 같은 이름(test1)으로 재생성하고 inode를 보자
$ rm test1
$ touch test2
$ ls -i test2
>>>
14434 test2
$ touch test1
$ ls -i test1
>>>
14437 test1
/dev
아래 위치$ ls -lah /dev
>>>
crw-rw-rw- 1 root root 1, 3 Feb 23 1999 null # major device number : 1 minor device number : 3
crw-rw-rw- 1 root root 1, 5 Feb 23 1999 zero # major device number : 1 minor device number : 5
crw------- 1 rubini tty 4, 1 Aug 16 22:22 tty1 # major device number : 4 minor device number : 1
crw-rw-rw- 1 root dialout 4, 64 Jun 30 11:19 ttyS0 # major device number : 4 minor device number : 64
crw-rw-rw- 1 root dialout 4, 65 Aug 16 00:00 ttyS1 # major device number : 4 minor device number : 65
c
) : terminal, keyboard, sound card, ...d
) : hard disk, RAM, CD-ROM, ...{ip주소}:5601
입력12.34.567.890:5601
test_index
index가 이미 존재하는 상황에서test_type
type을 생성하고,message
field(text)와 number
field(integer) mapping를 생성하는 작업PUT test_index/test_type/_mapping
{
"properties": {
"message": {
"type": "text"
},
"number" : {
"type": "integer"
}
}
}
test_index
index에 test_type
type을 생성하고,message
field(text)와 number
field(integer) mapping를 생성하는 작업PUT test_index
{
"mappings": {
"test_type": {
"properties": {
"message": {
"type": "text"
},
"number" : {
"type": "integer"
}
}
}
}
}
아니오
Time Picker의 Time Range은 Quick
, Relative
, Absolute
로 설정 가능
YYYY-MM-DD HH:mm:ss.SSS
형식만 인식단,
Management
- Advanced Settings
- dateFormat
수정 : X
혹은 x
Default: MMMM Do YYYY, HH:mm:ss.SSS
X
normalizer
를 사용한다
PUT test
{
"mappings": {
"test": {
"properties": {
"country": {
"type": "keyword"
}
}
}
}
}
country
field에 아래와 같이 데이터를 넣자PUT test/test/1
{
"country" : "China"
}
PUT test/test/2
{
"country" : "chile"
}
country
로 정렬해보자#22 와 같은 이유로 China
가 먼저 오고 chile
가 온다.
만약에 case-insensitive
하게(=이 경우 대소문자 무시) 결과를 보고 싶으면 아래와 normalizer를 추가한다
PUT test2
{
"settings": {
"analysis": {
"normalizer": {
"my_normalizer": {
"type": "custom",
"char_filter": [],
"filter": ["lowercase", "asciifolding"]
}
}
}
},
"mappings": {
"test2": {
"properties": {
"country": {
"type": "keyword",
"normalizer": "my_normalizer"
}
}
}
}
}
PUT test2/test2/1
{
"country" : "China"
}
PUT test2/test2/2
{
"country" : "chile"
}
China
와 china
와 같은 value가 있을 경우 통합되므로 주의하자Term Query를 사용하다보면 두 가지 방식이 있다는 걸 알 수 있다.
GET shopping/_search
{
"query": {
"term": {
"상품분류": "셔츠"
}
}
}
GET shopping/_search
{
"query": {
"term": {
"상품분류": {
"value": "셔츠"
}
}
}
}
결과는 같은데 확장형이 있는 이유는 term query와 함께 parameter를 사용하기 위해서이다. 예를 들어 아래와 같은 쿼리를 보자.
GET shopping/_search
{
"query": {
"bool": {
"should": [
{
"term": {
"상품분류": {
"value": "셔츠",
"boost": 2.0
}
}
},
{
"term": {
"고객주소_시도": "경상남도"
}
}
]
}
}
}
이처럼 확장형을 이용하면 term query와 boost
와 같은 parameter를 함께 사용할 수 있다.
이미지가 위치한 URL을 입력하면 Kibana에 직접 이미지를 보여줄 수 있다
PUT higee_image
{
"mappings": {
"test" : {
"properties": {
"image" : {
"type": "keyword"
},
"source" : {
"type": "keyword"
}
}
}
}
}
POST higee_image/test
{
"image" : "dog",
"source" : "https://www.flaticon.com/free-icon/dog_616408"
}
POST higee_image/test
{
"image" : "crab",
"source" : "https://www.flaticon.com/free-icon/crab_1009977"
}
POST higee_image/test
{
"image" : "rubber-ring",
"source" : "https://www.flaticon.com/free-icon/rubber-ring_1087315"
}
POST higee_image/test
{
"image" : "teddy-bear",
"source" : "https://www.flaticon.com/free-icon/teddy-bear_1083850"
}
POST higee_image/test
{
"image" : "whale",
"source" : "https://www.flaticon.com/free-icon/whale_866404"
}
가능하다
1. .kibana/index-pattern
에서 time filter field를 변경하려는 index
의 _id
를 찾는다
index : scenario1_higee
GET .kibana/index-pattern/_search
{
"_source" : "_id",
"query" : {
"match" : {
"title": "scenario1_higee"
}
}
}
2. 위에서 찾은 _id
값과 변경하려는 time filter field 이름을 아래처럼 넣어서 실행한다
_id : AWD-qHIXPloSIAlpN7ee
변경하려는 time filter field : 주문시간
POST /.kibana/index-pattern/AWD-qHIXPloSIAlpN7ee/_update
{
"doc": {
"timeFieldName" : "주문시간"
}
}
must
혹은 must_not
과 함께 사용할 경우 : 0must
혹은 must_not
없이 should
만 사용할 경우 : 1--
minimum_should_match
를 명시적으로 설정하지 않을 경우 : 1806
GET shopping/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"결제카드": "우리"
}
}
],
"should": [
{
"match": {
"고객성별": "남성"
}
}
]
}
}
}
minimum_should_match
를 0
으로 설정하는 경우 : 1806
GET shopping/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"결제카드": "우리"
}
}
],
"should": [
{
"match": {
"고객성별": "남성"
}
}
],
"minimum_should_match": 0
}
}
}
must
혹은 must_not
없이 should
만 사용minimum_should_match
를 명시적으로 설정하지 않는 경우 : 2714
GET shopping/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"고객성별": "남성"
}
}
]
}
}
}
minimum_should_match
를 명시적으로 1
로 설정하는 경우 : 2714
GET shopping/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"고객성별": "남성"
}
}
],
"minimum_should_match": 1
}
}
}
schedule
을 통해서 가능합니다.
schedule
없이 실행해보자input {
jdbc {
jdbc_validate_connection => true
jdbc_connection_string => "jdbc:mysql://13.125.21.52:3306/fc"
jdbc_user => "fc"
jdbc_password => "fc"
jdbc_driver_library => "/home/ec2-user/fc/logstash-5.6.4/driver/mysql-connector-java-5.1.36/mysql-connector-java-5.1.36-bin.jar"
jdbc_driver_class => "com.mysql.jdbc.Driver"
statement => "SELECT * FROM fc WHERE id > :sql_last_value"
use_column_value => true
tracking_column => "id"
}
}
output {
stdout {
codec => rubydebug
}
}
DB에서 데이터를 조회한 후 에 종료된다
schedule
옵션 (매 초 조회) 을 넣어서 실행해보자input {
jdbc {
jdbc_validate_connection => true
jdbc_connection_string => "jdbc:mysql://13.125.21.52:3306/fc"
jdbc_user => "fc"
jdbc_password => "fc"
jdbc_driver_library => "/home/ec2-user/fc/logstash-5.6.4/driver/mysql-connector-java-5.1.36/mysql-connector-java-5.1.36-bin.jar" jdbc_driver_class => "com.mysql.jdbc.Driver"
statement => "SELECT * FROM fc"
schedule => "* * * * * *"
use_column_value => true
tracking_column => "id"
}
}
output {
stdout {
codec => rubydebug
}
}
멈추지 않고 계속 동작하나 같은 데이터
를 계속 읽어온다
input {
jdbc {
jdbc_validate_connection => true
jdbc_connection_string => "jdbc:mysql://13.125.21.52:3306/fc"
jdbc_user => "fc"
jdbc_password => "fc"
jdbc_driver_library => "/home/ec2-user/fc/logstash-5.6.4/driver/mysql-connector-java-5.1.36/mysql-connector-java-5.1.36-bin.jar"
jdbc_driver_class => "com.mysql.jdbc.Driver"
statement => "SELECT * FROM fc WHERE id > :sql_last_value"
use_column_value => true
tracking_column => "id"
schedule => "* * * * * *"
}
}
output {
stdout {
codec => rubydebug
}
}
{
"tt": {
"mappings": {
"tt": {
"properties": {
"번호": {
"type": "integer"
},
"시간": {
"type": "date"
},
"이름": {
"type": "keyword"
},
"이메일": {
"type": "text"
}
}
}
}
}
}
GET tt/_search
{
"query" : {
"match_all" : {}
}
}
잘보면 integer
로 등록한 번호에 "1"이라는 string(keyword)가 들어가있고, keyword
로 등록한 "이름"에 5라는 numeric type이 들어가 있다.
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 3,
"max_score": 1,
"hits": [
{
"_index": "tt",
"_type": "tt",
"_id": "2",
"_score": 1,
"_source": {
"번호": 1,
"이름": "higee"
}
},
{
"_index": "tt",
"_type": "tt",
"_id": "1",
"_score": 1,
"_source": {
"번호": 1
}
},
{
"_index": "tt",
"_type": "tt",
"_id": "3",
"_score": 1,
"_source": {
"번호": "1",
"이름": 5
}
}
]
}
}
이름
Field
GET tt/_search
{
"size": 0,
"query": {
"match_all": {}
},
"aggs": {
"test_agg": {
"sum": {
"field": "이름"
}
}
}
}
{
"error": {
"root_cause": [
{
"type": "illegal_argument_exception",
"reason": "Expected numeric type on field [이름], but got [keyword]"
}
],
"type": "search_phase_execution_exception",
"reason": "all shards failed",
"phase": "query",
"grouped": true,
"failed_shards": [
{
"shard": 0,
"index": "tt",
"node": "eJL5WBAkTlCts8NhtI_dmA",
"reason": {
"type": "illegal_argument_exception",
"reason": "Expected numeric type on field [이름], but got [keyword]"
}
}
]
},
"status": 400
}
번호
Field
GET tt/_search
{
"size": 0,
"query": {
"match_all": {}
},
"aggs": {
"test_agg": {
"sum": {
"field": "번호"
}
}
}
}
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 3,
"max_score": 0,
"hits": []
},
"aggregations": {
"test_sum": {
"value": 3
}
}
}
단순히 Elasticsearch가 허용하기 때문이다. 강제로 막으려면 아래와 같이 할 수 있다
특정 Field 단위
number_two
Field는 Integer Type만 허용한다PUT my_index
{
"mappings": {
"my_type": {
"properties": {
"number_one": {
"type": "integer"
},
"number_two": {
"type": "integer",
"coerce": false
}
}
}
}
}
PUT my_index/my_type/5
{
"number_two" : 1.3
}
특정 Index 전체
PUT my_index
{
"settings": {
"index.mapping.coerce": false
},
"mappings": {
"my_type": {
"properties": {
"number_one": {
"type": "integer"
},
"number_two": {
"type": "integer"
}
}
}
}
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.