2015年6月26日 星期五

Log Server(elasticsearch+logstash+kopf+kibana)

這邊希望達到的目標: 每次有人使用 server service 時,log 紀錄便會依據 logstash 的 filter 規則處理過後存進 elasticsearch ,然後使用者能用 kibana 來看到分析該些 log 資料的視覺圖

上述流程如下圖所示

其它套件: 
kopf: 用來觀看 elasticsearch server 的各種狀態以及測試搜尋等功能
web service: flask(web application framework) + nginx(web server)


  • get app code
you can use any service code you want

here I use our own service code

first git clone position-engine.git from git

can see app.py (main service file) & .git

need to run service with flask

  • install flask use virtualenv


create virtualenv "flask_env" with python3
  • virtualenv -p python3 flask_env

switch environment to flask_env
  • . flask_env/bin/activate

if you can see the environment name in front of the user name, means you success activate the environment


in flask_env, install Flask
  • pip install Flask

this is for position-engine
  • pip install requests

in virtualenv remember do not use sudo command to install package
it's will install packages as root which means install packages out of virtualenv 

  • run flask service
when install finish, use python command can run the service
  • python app.py


if you see the info like the pic, you success

  • nginx
but now we don't have any web service for outside user to acccess
so we need to setup nginx for service

install nginx

  • sudo apt-get install nginx

In Ubuntu 14.04, by default, Nginx automatically starts when it is installed.

and now you access your IP address can see the welcome page

nginx service command

start service
  • sudo service nginx start

stop service
  • sudo service nginx stop

restart service
  • sudo service nginx restart

make sure web server will restart automatically when the server is rebooted by typing:

  • sudo update-rc.d nginx defaults


 setting nginx configuration 

now we want to manage the request which service should process

in this case we need use port 8120 to access the flask service

  1. copy one new file in /etc/nginx/sites-available/
  • sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/example
  • sudo vim /etc/nginx/sites-available/example

add 

location /position-service {
        proxy_pass the url your want to access;
}

when finish modify, set link to /etc/nginx/sites-enable/ dir
  • sudo ln -s /etc/nginx/sites-available/example /etc/nginx/sites-enabled/example

delete default link(sites-enable/default) or comment the code in this file(sites-available/default)
  • sudo rm /etc/nginx/sites-enable/default
you can use -s reload command to reload your setting without stop the service
  • sudo nginx -s reload

now you can access the service use this url:
http://IP address/position-service


  • Elasticsearch


照著上面的連結設定好後,理論上可以從設好的位置看到 kopf 的頁面


上面那條 header 應該會是綠色的,但因為我的副本數只有 1,在這樣的情況下基於安全考量 kopf 會提醒你這是不健康的狀態,只要把副本數往上調即可變回綠色的狀態(2016-01-27 update)

  • Logstash
這邊安裝的是 Logstash 1.5.1 版


下載/安裝
  • sudo dpkg -i logstash_1.5.1-1_all.deb


啟動
  • sudo service logstash start

輸入這個指令就能讓 logstash 的服務啟動


如果你是第一次啟動的話,理論上來說去 log 檔裡應該會看到找不到 config files 的這則訊息



這時有兩種解法

1. 在 /etc/logstash/conf.d/ 目錄底下新增 logstash.conf 的檔案,然後啟動服務
sudo service logstash start

2. 使用 -f  指令去運行指定的 logstash.conf 檔案
/opt/logstash/bin/logstash -f logstash.conf

而 logstash.conf 撰寫的部分在下面的設定 config 檔章節會在進行說明


logstash service 的指令其實是一隻 script,這隻 script 會根據你後面代的參數去 /etc/init.d/ 資料夾裡找相對應的服務

這個 service 的指令其實是一隻 script,這隻 script 會根據你後面代的參數去 /etc/init.d/ 資料夾裡找相對應的服務

也就是說其實你可以去 /etc/init.d/logstash 檔案裡看更多啟動的相關資訊
一般比較需要知道的有 
  • log 存放位置: /var/log/logstash/
  • config 檔案存放位置: /etc/logstash/conf.d/
  • 主程式存放位置: /opt/logstash/bin/logstash/

P.S. 這邊是用 deb 檔安裝所以預設位置都設好了,如果是 git 下載檔案運行的話,直接在下載的 logstash 資料夾裡下 bin/logstash 指令即可


設定 config 檔

logstash 的 config 檔可以用 json 格式來撰寫

主要分成 input/filter/output 三個區塊

  • input
直接拿 nginx 的 access.log 來使用,logstash 有支援直接讀取檔案的功能
  • path: 讀取檔案位置
  • type: 存進 elasticsearch 後這會變成其中一個辨識欄位
  • start_position: 要從何處開始讀取資料 "begging" or "end",預設是 "end"

logstash 讀取檔案的機制是每當檔案有變動時就會啟動

  • filter

可以用 grok debugger 來看寫的規則是否能正確 filter 出資訊
grok debugger: https://grokdebug.herokuapp.com/
grok debugger 上手教學: https://www.youtube.com/watch?v=YIKm6WUgFTY

access log:
%{IP:client_ip} - (?<client_user>[a-zA-Z\.\@\-\+_%]+) \[%{HTTPDATE:access_time}\] "%{WORD:method} (?<request_url>%{URIPATHPARAM}|%{URI}) HTTP/%{NUMBER:http_version}" %{NUMBER:response} (?:%{NUMBER:bytes}|-) (?<from>%{QS}) %{QS:other}

error log:
[
(?<access_time>\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}) \[%{DATA:severity}\] (%{NUMBER:pid}#%{NUMBER}: \*%{NUMBER}|\*%{NUMBER}) %{DATA:err_message}(?:, client: (?<client_ip>%{IP}|%{HOSTNAME}))(?:, server: (?<server_ip>%{IPORHOST}|%{QS}))(?:, request: %{QS:request})?(?:, host: %{QS:host_info})
,
(?<access_time>\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}) \[%{DATA:severity}\] (%{NUMBER:pid}#%{NUMBER}: \*%{NUMBER}|\*%{NUMBER}) %{DATA:err_message}(?:, client: (?<client_ip>%{IP}|%{HOSTNAME}))(?:, server: (?<server_ip>%{IPORHOST}|%{QS}))(?:, request: %{QS:request})?(?:, upstream: %{QS:upstream})?(?:, host: "(?<host_info>%{IPORHOST}|(?<host_ip>%{IP}):%{POSINT:host_port})")
]

  • output
output 這部分是有用到 elasticsearch 這個 plugin 
  • host: 資料要存放的地方,這邊因為前面設定 elasticsearch 的時候有設定過 hosts,所以直接輸入 name 就 OK 了,不然就要輸入 IP:port,這邊要注意的是要使用的 port 是 9300,不是 9200

設定完後可以用這個測試 config 檔案格式是否正確
/opt/logstash/bin/logstash agent -f logstash.conf --configtest


config reference


  • Kibana
log 分析視覺化頁面

這邊安裝的是 4.1.0 版,linux 64-bit 版本,直接下載官網的壓縮檔案

下載/安裝
  • sudo tar zcvf kibana-4.1.0-linux-x64.tar.gz

要使用 Kibana 的話,很多人是建議要把 ruby 環境建置好...
  • sudo apt-get install ruby
  • sudo apt-get install rubygems-integration
  • sudo gem install bundler

但我遇到的問題是沒有 "jade" 這個 module

所以我安裝了 npm 系列去安裝 "jade" 這個 module

  • sudo apt-get install npm 
  • sudo npm install jade

先進入解壓縮完的資料夾,輸入
  • bin/kibana

就能成功啟動 kibana 服務

nginx setting




一開始 kibana 會讓你選擇 index,如果前面的 log 資料有順利存進 elasticsearch 的話,就能直接選到,接著就能進入 kibana 的分析頁面了

2014年12月8日 星期一

android studio setup 紀錄

一般來說一路按下一步到底就好

有一個地方偵測JDK 7 or JDK 7以上版本的地方一直偵測不到

跳出的提示視窗有說如果偵測不到的話可以用手動加入的方式

找到java jdk底下的bin/java.exe檔,再加入到環境變數的path中(如下圖)












加完後便不會有偵測失敗的問題

(目前直接使用JDK8的版本,不知道支援度如何,先測試看看)

2014年11月3日 星期一

python logging

最近有實際需求後才發現logging是個相當好用記錄log的model...

所以在這邊稍微記錄一下平常使用logging的方法

這程式主要的功能是希望每次運行程式後能將log續寫至指定的檔案

並在檔案達到maxBytes指定的大小後便新增新的log檔

新增到backupCount指定的數量後刪除最舊的檔案


# -*- coding: utf-8 -*-

import logging

from logging.handlers import RotatingFileHandler

def log():

        file_path = '{0}/file name'.format(os.path.dirname(os.path.abspath(__file__)))

        # maxBytes => 多少bytes後就開始切檔案,這裡設定2M

        # backupCount => 檔案大於幾個後會開始刪除舊檔案,以這邊為例子就是新增10個檔案後便會開始刪除舊的檔案

        Rhandler = RotatingFileHandler(file_path, maxBytes=2*1024*1024, backupCount=10)

        logger = logging.getLogger(__name__)

        logger.setLevel(logging.INFO)

        logger.addHandler(Rhandler)



        logger.debug("debug message ... 1")

        logger.info("info message ... 2")

        logger.warn("warn message ... 3")

        logger.error("error message ... 4")

if __name__ == '__main__':
        log()

2014年10月29日 星期三

Amazon RDS backup snapshot using python

看很多Amazon auto backup都是直接下指令排序在建立snapshot

由於對ubuntu指令不是很熟,所以就用python來撰寫一個自動建立snapshot的程式,超過15天的snapshot就刪除

系統: ubuntu 14.04

事先安裝:
1. sudo apt-get install awscli
2. sudo apt-get install rdscli 
(這個應該是可以不用安裝,沒試過解除安裝後是否可以運行,有興趣的可以自行嘗試看看)
3. sudo pip install boto
(Python package that provides interfaces to Amazon Web Services)


code example:
# -*- coding: utf-8 -*-
import boto.rds, datetime, time

def day_process(date):
        # process rds iso datetime return format => '%Y-%m-%d' for calculate
        d = datetime.datetime.strptime(str(date), '%Y-%m-%dT%H:%M:%S.%fZ')
        d = datetime.datetime.strptime(str(d).split(" ")[0], '%Y-%m-%d')
        return d


# connect rds
conn = boto.rds.connect_to_region('your rds region'\
        , aws_access_key_id = 'your aws_access_key_id'\
        , aws_secret_access_key = 'your aws_secret_access_key')

# today date
NToday = datetime.datetime.today().strftime('%Y-%m-%d')

# db instance list
db_instance = [put your instance list here, so we can use it for loop to process all db instance]

for db in db_instance:

    # get all db instance snapshot
    snap = conn.get_all_dbsnapshots(None, db, None, None)

    # snapshot list for snapshot id, snapshot_create_time
    snapshot_list = []
    for v in snap:
        temp_dict = {}
        temp_dict['snap_id'] = v.id
        temp_dict['snap_time'] = v.snapshot_create_time
        snapshot_list.append(temp_dict)

    # sort snapshot list
    sort_list = sorted(snapshot_list, key = lambda x:x['snap_time'])

    try:
        # create daily snapshot
        conn.create_dbsnapshot("{0}-{1}".format(db, NToday), db)

        # latest_day & oldest_day & se_lat_day
        latest_day = datetime.datetime.strptime(NToday, '%Y-%m-%d')
        se_lat_day = day_process(sort_list[len(sort_list)-1]['snap_time'])
        oldest_day = day_process(sort_list[0]['snap_time'])

        # snapshot keep days
        keep_day = 15

        if (latest_day-oldest_day).days and (se_lat_day-oldest_day).days >= keep_day:
            # for the situation over 2 snapshot > keep_day
            for li in sort_list:
                com_day_old = day_process(li['snap_time'])
                if (latest_day-com_day_old).days > keep_day:
                    conn.delete_dbsnapshot(li['snap_id'])

        if (latest_day-oldest_day).days >= keep_day:
            # for the situation only 1 snapshot > keep_day
            # delete snapshot
            conn.delete_dbsnapshot(sort_list[0]['snap_id'])

    except Exception as e:
        print e

2014年6月8日 星期日

android hash key 紀錄

這篇是將之前做過的FB相關應用前置動作紀錄一下

首先要產生hash key這篇就講得蠻清楚的了
http://blog.kenyang.net/2012/01/androidfacebook-login-api.html

再來是要怎樣將產生的key包進要生成的application裡(如下圖)






2013年12月19日 星期四

android.os.NetworkOnMainThreadException的問題

自從將 Android 升級成 4.2 之後,httpRequest 就出現了 android.os.NetworkOnMainThreadException 這個問題

在網路上查過之後才發現原來 Android 為了怕開發者將 HTTP 傳送請求放在主程式中,如果 HTTP 這個要求一直持續的話很有可能會卡死整個程式,造成應用程式的 crash 因此 Android 非常貼心的幫助開發者判斷 httpRequest 這個方法是否是在主程式中運行,如果是的話便會拋出錯誤

而如果想要解決這問題就是要將 httpRequest 寫在多執行緒中,以下是其中一種解法

public class MainActivity extends Activity{
 
 private EditText txtMessage;
    private Button sendBtn;
    private String uriAPI = "你要傳送要求的網址";
    String msg = null;
    @Override

    public void onCreate(Bundle savedInstanceState){

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        txtMessage = (EditText) findViewById(R.id.txt_message);
        sendBtn = (Button) findViewById(R.id.send_btn);
        sendBtn.setOnClickListener(sendlistener);
        
    }
    private Runnable mutiThread = new Runnable(){
      public void run(){
      // 運行網路連線的程式
      msg = txtMessage.getEditableText().toString();
      String r = sendPostDataToInternet(msg);
      if (r != null)
      Log.d("first0.0", r);

    }
  };
  public OnClickListener sendlistener = new OnClickListener(){
    public void onClick(View v){
      if (v == sendBtn){
        if (txtMessage != null){
              /* 
               * 這邊要用 Thread 是因為 Android 改版之後
               * 會對在主程式裡跑網路連接的程式碼做 Exception 的意外排除動作
               * 因此要把網路連線使用多執行緒的方式去運行,才不會被當成例外錯誤拋出
               */
               Thread thread = new Thread(mutiThread);
               thread.start();
         }
       }
     }
   };
    
    
    private String sendPostDataToInternet(String strTxt){

        /* HTTP Post */
        HttpPost httpRequest = new HttpPost(uriAPI);

        /* 將 Post 值存為一個 NameValuePair[],在這邊去添加自己想要的值 */
        List params = new ArrayList();

        params.add(new BasicNameValuePair("APIMethod", "phoneTest"));
        /* 這邊 PHP 端的接法就是用 $_POST['data']就接的到這邊的 strTxt 這個變數值了 */
        try{
         
            /* 設定 HTTP request */
            httpRequest.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));

            /* 傳送 HTTP response */
            HttpResponse httpResponse = new DefaultHttpClient().execute(httpRequest);
            
            /* 如果回傳值為 200 即為傳送 ok */
            if (httpResponse.getStatusLine().getStatusCode() == 200){

             /* 接收回傳字串,這邊接的值就是在 PHP 那邊 echo 的值 */ 
             /* hellow.php 這隻程式碼的寫法:
              * if($_POST['data']!=null) echo $_POST['data']; */
              
                String strResult = EntityUtils.toString(httpResponse.getEntity());
            
                return strResult;
            }
            else{
                Log.d("sss", "no message");
            }
        } catch (ClientProtocolException e){
         Log.d("@@", e.toString());
        } catch (IOException e){
         Log.d("@@", e.toString());
        } catch (Exception e){
         Log.d("@@", e.toString());
        }
        return null;
    }
}

2013年8月13日 星期二

Facebook API Heroku

登入: heroku login

金鑰重新加入的解法: heroku keys:add





https://devcenter.heroku.com/articles/facebook

因為每次都會忘記,所以稍微記錄一下